diff --git a/bin/anyproxy b/bin/anyproxy
index 537cbfb..d373535 100755
--- a/bin/anyproxy
+++ b/bin/anyproxy
@@ -6,7 +6,7 @@ const program = require('commander'),
color = require('colorful'),
co = require('co'),
packageInfo = require('../package.json'),
- util = require('../dist/util'),
+ util = require('../dist/util').default,
rootCACheck = require('./rootCaCheck'),
startServer = require('./startServer'),
certMgr = require('../dist/certMgr'),
diff --git a/lib/certMgr.js b/lib/certMgr.js
index 1136ad8..07e73e4 100644
--- a/lib/certMgr.js
+++ b/lib/certMgr.js
@@ -5,7 +5,7 @@ const co = require('co');
const os = require('os');
const inquirer = require('inquirer');
-const util = require('./util');
+const util = require('./util').default;
const logUtil = require('./log');
const options = {
diff --git a/lib/httpsServerMgr.js b/lib/httpsServerMgr.js
index e66c840..6ce6380 100644
--- a/lib/httpsServerMgr.js
+++ b/lib/httpsServerMgr.js
@@ -8,7 +8,7 @@ const async = require('async'),
color = require('colorful'),
certMgr = require('./certMgr'),
logUtil = require('./log'),
- util = require('./util'),
+ util = require('./util').default,
wsServerMgr = require('./wsServerMgr'),
co = require('co'),
constants = require('constants'),
@@ -194,4 +194,4 @@ class httpsServerMgr {
}
}
-module.exports = httpsServerMgr;
+export default httpsServerMgr;
diff --git a/lib/log.ts b/lib/log.ts
index 1cbf52c..e4a02e4 100644
--- a/lib/log.ts
+++ b/lib/log.ts
@@ -1,109 +1,109 @@
'use strict';
- import * as color from 'colorful';
- import util from './util';
+import * as color from 'colorful';
+import util from './util';
- let ifPrint = true;
- let logLevel = 0;
- enum LogLevelMap {
- tip = 0,
- system_error = 1,
- error = 1,
- rule_error = 2,
- warn = 3,
- debug = 4,
- };
+let ifPrint = true;
+let logLevel = 0;
+enum LogLevelMap {
+ tip = 0,
+ system_error = 1,
+ error = 1,
+ rule_error = 2,
+ warn = 3,
+ debug = 4,
+};
- function setPrintStatus(status: boolean): void {
- ifPrint = !!status;
+function setPrintStatus(status: boolean): void {
+ ifPrint = !!status;
+}
+
+function setLogLevel(level: string): void {
+ logLevel = parseInt(level, 10);
+}
+
+function printLog(content: string, type?: LogLevelMap) {
+ if (!ifPrint) {
+ return;
}
- function setLogLevel(level: string): void {
- logLevel = parseInt(level, 10);
- }
-
- function printLog(content: string, type?: LogLevelMap) {
- if (!ifPrint) {
- return;
+ const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss');
+ switch (type) {
+ case LogLevelMap.tip: {
+ if (logLevel > 0) {
+ return;
+ }
+ console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
+ break;
}
- const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss');
- switch (type) {
- case LogLevelMap.tip: {
- if (logLevel > 0) {
- return;
- }
- console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
- break;
+ case LogLevelMap.system_error: {
+ if (logLevel > 1) {
+ return;
}
+ console.error(color.red(`[AnyProxy ERROR][${timeString}]: ` + content));
+ break;
+ }
- case LogLevelMap.system_error: {
- if (logLevel > 1) {
- return;
- }
- console.error(color.red(`[AnyProxy ERROR][${timeString}]: ` + content));
- break;
- }
-
- case LogLevelMap.rule_error: {
- if (logLevel > 2) {
- return;
- }
-
- console.error(color.red(`[AnyProxy RULE_ERROR][${timeString}]: ` + content));
- break;
- }
-
- case LogLevelMap.warn: {
- if (logLevel > 3) {
- return;
- }
-
- console.error(color.magenta(`[AnyProxy WARN][${timeString}]: ` + content));
- break;
- }
-
- case LogLevelMap.debug: {
- console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
+ case LogLevelMap.rule_error: {
+ if (logLevel > 2) {
return;
}
- default: {
- console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
- break;
+ console.error(color.red(`[AnyProxy RULE_ERROR][${timeString}]: ` + content));
+ break;
+ }
+
+ case LogLevelMap.warn: {
+ if (logLevel > 3) {
+ return;
}
+
+ console.error(color.magenta(`[AnyProxy WARN][${timeString}]: ` + content));
+ break;
+ }
+
+ case LogLevelMap.debug: {
+ console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
+ return;
+ }
+
+ default: {
+ console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
+ break;
}
}
+}
- module.exports.printLog = printLog;
+module.exports.printLog = printLog;
- function debug (content): void {
- printLog(content, LogLevelMap.debug);
- };
+function debug (content): void {
+ printLog(content, LogLevelMap.debug);
+};
- function info (content): void {
- printLog(content, LogLevelMap.tip);
- };
+function info (content): void {
+ printLog(content, LogLevelMap.tip);
+};
- function warn (content) {
- printLog(content, LogLevelMap.warn);
- };
+function warn (content) {
+ printLog(content, LogLevelMap.warn);
+};
- function error (content) {
- printLog(content, LogLevelMap.system_error);
- };
+function error (content) {
+ printLog(content, LogLevelMap.system_error);
+};
- function ruleError (content) {
- printLog(content, LogLevelMap.rule_error);
- };
+function ruleError (content) {
+ printLog(content, LogLevelMap.rule_error);
+};
- module.exports.setPrintStatus = setPrintStatus;
- module.exports.setLogLevel = setLogLevel;
- module.exports.T_TIP = LogLevelMap.tip;
- module.exports.T_ERR = LogLevelMap.system_error;
- module.exports.T_RULE_ERROR = LogLevelMap.rule_error;
- module.exports.T_WARN = LogLevelMap.warn;
- module.exports.T_DEBUG = LogLevelMap.debug;
+module.exports.setPrintStatus = setPrintStatus;
+module.exports.setLogLevel = setLogLevel;
+module.exports.T_TIP = LogLevelMap.tip;
+module.exports.T_ERR = LogLevelMap.system_error;
+module.exports.T_RULE_ERROR = LogLevelMap.rule_error;
+module.exports.T_WARN = LogLevelMap.warn;
+module.exports.T_DEBUG = LogLevelMap.debug;
const LogUtil = {
setPrintStatus,
@@ -118,8 +118,8 @@ const LogUtil = {
T_ERR: LogLevelMap.error,
T_RULE_ERROR: LogLevelMap.rule_error,
T_WARN: LogLevelMap.warn,
- T_DEBUG: LogLevelMap.debug
-}
+ T_DEBUG: LogLevelMap.debug,
+};
+exports.LogUtil = LogUtil;
export default LogUtil;
-module.exports = LogUtil;
diff --git a/lib/proxy.js b/lib/proxy.js
index cbeebfd..85c25bc 100644
--- a/lib/proxy.js
+++ b/lib/proxy.js
@@ -7,7 +7,7 @@ const http = require('http'),
certMgr = require('./certMgr'),
Recorder = require('./recorder'),
logUtil = require('./log'),
- util = require('./util'),
+ util = require('./util').default,
events = require('events'),
co = require('co'),
WebInterface = require('./webInterface'),
@@ -114,7 +114,7 @@ class ProxyCore extends events.EventEmitter {
this.recorder = config.recorder;
// init request handler
- const RequestHandler = util.freshRequire('./requestHandler');
+ const RequestHandler = util.freshRequire('./requestHandler').default;
this.requestHandler = new RequestHandler({
wsIntercept: config.wsIntercept,
httpServerPort: config.port, // the http server port for http proxy
diff --git a/lib/recorder.js b/lib/recorder.js
index 1a37739..f684a49 100644
--- a/lib/recorder.js
+++ b/lib/recorder.js
@@ -8,7 +8,7 @@ const Datastore = require('nedb'),
events = require('events'),
iconv = require('iconv-lite'),
fastJson = require('fast-json-stringify'),
- proxyUtil = require('./util');
+ proxyUtil = require('./util').default;
const wsMessageStingify = fastJson({
title: 'ws message stringify',
@@ -333,3 +333,4 @@ class Recorder extends events.EventEmitter {
}
module.exports = Recorder;
+module.exports.default = Recorder;
diff --git a/lib/requestHandler/CommonReadableStream.js b/lib/requestHandler/CommonReadableStream.js
deleted file mode 100644
index d6ec062..0000000
--- a/lib/requestHandler/CommonReadableStream.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const Readable = require('stream').Readable;
-
-const DEFAULT_CHUNK_COLLECT_THRESHOLD = 20 * 1024 * 1024; // about 20 mb
-
-class CommonReadableStream extends Readable {
- constructor(config) {
- super({
- highWaterMark: DEFAULT_CHUNK_COLLECT_THRESHOLD * 5
- });
- }
- _read(size) {
-
- }
-}
-
-module.exports = CommonReadableStream;
diff --git a/lib/requestHandler/CommonReadableStream.ts b/lib/requestHandler/CommonReadableStream.ts
new file mode 100644
index 0000000..8c85901
--- /dev/null
+++ b/lib/requestHandler/CommonReadableStream.ts
@@ -0,0 +1,17 @@
+import * as stream from 'stream';
+const Readable = stream.Readable;
+const DEFAULT_CHUNK_COLLECT_THRESHOLD = 20 * 1024 * 1024; // about 20 mb
+
+/* tslint:disable:no-empty */
+class CommonReadableStream extends Readable {
+ constructor() {
+ super({
+ highWaterMark: DEFAULT_CHUNK_COLLECT_THRESHOLD * 5,
+ });
+ }
+ public _read(): void {
+
+ }
+}
+
+export default CommonReadableStream;
diff --git a/lib/requestHandler/UserReqHandler.js b/lib/requestHandler/UserReqHandler.ts
similarity index 77%
rename from lib/requestHandler/UserReqHandler.js
rename to lib/requestHandler/UserReqHandler.ts
index cdff75b..d1be4e0 100644
--- a/lib/requestHandler/UserReqHandler.js
+++ b/lib/requestHandler/UserReqHandler.ts
@@ -1,40 +1,64 @@
///
-import RequestErrorHandler from './requestErrorHandler';
+declare interface ErrorResponse {
+ statusCode: number;
+ header: OneLevelObjectType;
+ body: string;
+}
-const
- url = require('url'),
- https = require('https'),
- http = require('http'),
- color = require('colorful'),
- Buffer = require('buffer').Buffer,
- util = require('../util'),
- Stream = require('stream'),
- logUtil = require('../log'),
- CommonReadableStream = require('./CommonReadableStream'),
- zlib = require('zlib'),
- brotliTorb = require('brotli'),
- co = require('co');
+import * as url from 'url';
+import * as https from 'https';
+import * as http from 'http';
+import * as color from 'colorful';
+import * as buffer from 'buffer';
+import * as Stream from 'stream';
+import * as zlib from 'zlib';
+import * as brotliTorb from 'brotli';
+import * as co from 'co';
+import util from '../util';
+import logUtil from '../log';
+import RequestErrorHandler from './requestErrorHandler';
+import CommonReadableStream from './CommonReadableStream';
+import Recorder from '../recorder';
+const Buffer = buffer.Buffer;
+// const
+// url = require('url'),
+// https = require('https'),
+// http = require('http'),
+// color = require('colorful'),
+// Buffer = require('buffer').Buffer,
+// util = require('../util').default,
+// Stream = require('stream'),
+// logUtil = require('../log'),
+// CommonReadableStream = require('./CommonReadableStream'),
+// zlib = require('zlib'),
+// brotliTorb = require('brotli'),
+// co = require('co');
const requestErrorHandler = new RequestErrorHandler();
const DEFAULT_CHUNK_COLLECT_THRESHOLD = 20 * 1024 * 1024; // about 20 mb
// to fix issue with TLS cache, refer to: https://github.com/nodejs/node/issues/8368
-https.globalAgent.maxCachedSessions = 0;
+(https.globalAgent as any).maxCachedSessions = 0;
/**
* fetch remote response
*
* @param {string} protocol
- * @param {object} options options of http.request
- * @param {buffer} reqData request body
+ * @param {object} options
+ * @param {buffer} reqData
* @param {object} config
* @param {boolean} config.dangerouslyIgnoreUnauthorized
* @param {boolean} config.chunkSizeThreshold
* @returns
*/
-function fetchRemoteResponse(protocol, options, reqData, config) {
- reqData = reqData || '';
+function fetchRemoteResponse(
+ protocol: string, options: https.RequestOptions | http.RequestOptions ,
+ reqData: Buffer, config: {
+ dangerouslyIgnoreUnauthorized: boolean;
+ chunkSizeThreshold: number;
+ }): Promise {
+ reqData = reqData || Buffer.from('');
return new Promise((resolve, reject) => {
delete options.headers['content-length']; // will reset the content-length after rule
delete options.headers['Content-Length'];
@@ -42,17 +66,18 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
delete options.headers['transfer-encoding'];
if (config.dangerouslyIgnoreUnauthorized) {
- options.rejectUnauthorized = false;
+ (options as https.RequestOptions).rejectUnauthorized = false;
}
if (!config.chunkSizeThreshold) {
throw new Error('chunkSizeThreshold is required');
}
- //send request
- const proxyReq = (/https/i.test(protocol) ? https : http).request(options, (res) => {
+ const finalHttpModule: typeof https | typeof http = /https/i.test(protocol) ? https : http;
+ // send request
+ const proxyReq: http.ClientRequest = (finalHttpModule as any).request(options, (res: http.IncomingMessage): void => {
res.headers = util.getHeaderFromRawHeaders(res.rawHeaders);
- //deal response header
+ // deal response header
const statusCode = res.statusCode;
const resHeader = res.headers;
let resDataChunks = []; // array of data chunks or stream
@@ -69,9 +94,9 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
// remove gzip related header, and ungzip the content
// note there are other compression types like deflate
const contentEncoding = resHeader['content-encoding'] || resHeader['Content-Encoding'];
- const ifServerGzipped = /gzip/i.test(contentEncoding);
- const isServerDeflated = /deflate/i.test(contentEncoding);
- const isBrotlied = /br/i.test(contentEncoding);
+ const ifServerGzipped = /gzip/i.test((contentEncoding as string));
+ const isServerDeflated = /deflate/i.test((contentEncoding as string));
+ const isBrotlied = /br/i.test((contentEncoding as string));
/**
* when the content is unzipped, update the header content
@@ -82,10 +107,10 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
delete resHeader['content-encoding'];
delete resHeader['Content-Encoding'];
}
- }
+ };
// set origin content length into header
- resHeader['x-anyproxy-origin-content-length'] = originContentLen;
+ resHeader['x-anyproxy-origin-content-length'] = '' + originContentLen;
// only do unzip when there is res data
if (ifServerGzipped && originContentLen) {
@@ -133,7 +158,7 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
});
};
- //deal response data
+ // deal response data
res.on('data', (chunk) => {
rawResChunks.push(chunk);
if (resDataStream) { // stream mode
@@ -176,16 +201,16 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
/*
* get error response for exception scenarios
*/
-function getErrorResponse(error, fullUrl) {
+function getErrorResponse(error: NodeJS.ErrnoException, fullUrl: string): ErrorResponse {
// default error response
const errorResponse = {
statusCode: 500,
header: {
'Content-Type': 'text/html; charset=utf-8',
'Proxy-Error': true,
- 'Proxy-Error-Message': error ? JSON.stringify(error) : 'null'
+ 'Proxy-Error-Message': error ? JSON.stringify(error) : 'null',
},
- body: requestErrorHandler.getErrorContent(error, fullUrl)
+ body: requestErrorHandler.getErrorContent(error, fullUrl),
};
return errorResponse;
@@ -193,13 +218,16 @@ function getErrorResponse(error, fullUrl) {
export default class UserReqHandler {
- constructor(ctx, userRule, recorder) {
+ public userRule: AnyProxyRule;
+ public recorder: Recorder;
+ private reqHandlerCtx: any;
+ constructor(ctx: any, userRule: AnyProxyRule, recorder: Recorder) {
this.userRule = userRule;
this.recorder = recorder;
this.reqHandlerCtx = ctx;
}
- handler(req, userRes) {
+ public handler(req: http.IncomingMessage, userRes: http.ServerResponse): void {
/*
note
req.url is wired
@@ -208,7 +236,7 @@ export default class UserReqHandler {
*/
const self = this;
const host = req.headers.host;
- const protocol = (!!req.connection.encrypted && !(/^http:/).test(req.url)) ? 'https' : 'http';
+ const protocol = (!!(req.connection as any).encrypted && !(/^http:/).test(req.url)) ? 'https' : 'http';
const fullUrl = protocol === 'http' ? req.url : (protocol + '://' + host + req.url);
const urlPattern = url.parse(fullUrl);
@@ -246,10 +274,10 @@ export default class UserReqHandler {
const prepareRequestDetail = () => {
const options = {
hostname: urlPattern.hostname || req.headers.host,
- port: urlPattern.port || req.port || (/https/.test(protocol) ? 443 : 80),
+ port: urlPattern.port || (req as any).port || (/https/.test(protocol) ? 443 : 80),
path,
method: req.method,
- headers: req.headers
+ headers: req.headers,
};
requestDetail = {
@@ -257,7 +285,7 @@ export default class UserReqHandler {
protocol,
url: fullUrl,
requestData: reqData,
- _req: req
+ _req: req,
};
return Promise.resolve();
@@ -294,7 +322,7 @@ export default class UserReqHandler {
if (!responseInfo) {
throw new Error('failed to get response info');
} else if (!responseInfo.statusCode) {
- throw new Error('failed to get response status code')
+ throw new Error('failed to get response status code');
} else if (!responseInfo.header) {
throw new Error('filed to get response header');
}
@@ -326,7 +354,7 @@ export default class UserReqHandler {
}
return responseInfo;
- }
+ };
// fetch complete request data
co(fetchReqData)
@@ -342,29 +370,29 @@ export default class UserReqHandler {
protocol,
url: protocol + '://' + host + path,
req,
- startTime: new Date().getTime()
+ startTime: new Date().getTime(),
};
resourceInfoId = self.recorder.appendRecord(resourceInfo);
}
try {
- resourceInfo.reqBody = reqData.toString(); //TODO: deal reqBody in webInterface.js
+ resourceInfo.reqBody = reqData.toString(); // TODO: deal reqBody in webInterface.js
self.recorder && self.recorder.updateRecord(resourceInfoId, resourceInfo);
- } catch (e) { }
+ } catch (e) { console.error(e); }
})
// invoke rule before sending request
- .then(co.wrap(function *() {
+ .then(co.wrap(function*(): Generator {
const userModifiedInfo = (yield self.userRule.beforeSendRequest(Object.assign({}, requestDetail))) || {};
const finalReqDetail = {};
['protocol', 'requestOptions', 'requestData', 'response'].map((key) => {
- finalReqDetail[key] = userModifiedInfo[key] || requestDetail[key]
+ finalReqDetail[key] = userModifiedInfo[key] || requestDetail[key];
});
return finalReqDetail;
}))
// route user config
- .then(co.wrap(function *(userConfig) {
+ .then(co.wrap(function *(userConfig: AnyProxyRequestDetail): Generator {
if (userConfig.response) {
// user-assigned local response
userConfig._directlyPassToRespond = true;
@@ -379,7 +407,7 @@ export default class UserReqHandler {
statusCode: remoteResponse.statusCode,
header: remoteResponse.header,
body: remoteResponse.body,
- rawBody: remoteResponse.rawBody
+ rawBody: remoteResponse.rawBody,
},
_res: remoteResponse._res,
};
@@ -389,18 +417,19 @@ export default class UserReqHandler {
}))
// invoke rule before responding to client
- .then(co.wrap(function *(responseData) {
+ .then(co.wrap(function*(responseData: AnyProxyReponseDetail): Generator {
if (responseData._directlyPassToRespond) {
return responseData;
} else if (responseData.response.body && responseData.response.body instanceof CommonReadableStream) { // in stream mode
return responseData;
} else {
// TODO: err etimeout
- return (yield self.userRule.beforeSendResponse(Object.assign({}, requestDetail), Object.assign({}, responseData))) || responseData;
+ return (yield self.userRule.beforeSendResponse(
+ Object.assign({}, requestDetail), Object.assign({}, responseData))) || responseData;
}
}))
- .catch(co.wrap(function *(error) {
+ .catch(co.wrap(function *(error: NodeJS.ErrnoException): Generator {
logUtil.printLog(util.collectErrorLog(error), logUtil.T_ERR);
let errorResponse = getErrorResponse(error, fullUrl);
@@ -411,18 +440,18 @@ export default class UserReqHandler {
if (userResponse && userResponse.response && userResponse.response.header) {
errorResponse = userResponse.response;
}
- } catch (e) { }
+ } catch (e) { console.error(e); }
return {
- response: errorResponse
+ response: errorResponse,
};
}))
.then(sendFinalResponse)
- //update record info
+ // update record info
.then((responseInfo) => {
resourceInfo.endTime = new Date().getTime();
- resourceInfo.res = { //construct a self-defined res object
+ resourceInfo.res = { // construct a self-defined res object
statusCode: responseInfo.statusCode,
headers: responseInfo.header,
};
diff --git a/lib/requestHandler/index.js b/lib/requestHandler/index.ts
similarity index 77%
rename from lib/requestHandler/index.js
rename to lib/requestHandler/index.ts
index 76b375c..99e526d 100644
--- a/lib/requestHandler/index.js
+++ b/lib/requestHandler/index.ts
@@ -1,16 +1,31 @@
'use strict';
+///
import UserReqHandler from './UserReqHandler';
+import HttpsServerMgr from '../httpsServerMgr';
+import Recorder from '../recorder';
-const
- net = require('net'),
- color = require('colorful'),
- util = require('../util'),
- logUtil = require('../log'),
- co = require('co'),
- WebSocket = require('ws'),
- CommonReadableStream = require('./CommonReadableStream'),
- HttpsServerMgr = require('../httpsServerMgr');
+import * as color from 'colorful';
+import * as co from 'co';
+import * as net from 'net';
+import * as http from 'http';
+import * as WebSocket from 'ws';
+import util from '../util';
+import logUtil from '../log';
+import CommonReadableStream from './CommonReadableStream';
+
+interface IWsReqInfo {
+ headers: http.IncomingHttpHeaders;
+ noWsHeaders: http.IncomingHttpHeaders;
+ hostName: string;
+ port: string;
+ path: string;
+ protocol: 'wss' | 'ws';
+}
+
+interface IWebsocketOptionHeaders {
+ [key: string]: string;
+}
/**
* get request info from the ws client, includes:
@@ -22,7 +37,7 @@ const
@param @required wsClient the ws client of WebSocket
*
*/
-function getWsReqInfo(wsReq) {
+function getWsReqInfo(wsReq: http.IncomingMessage): IWsReqInfo {
const headers = wsReq.headers || {};
const host = headers.host;
const hostName = host.split(':')[0];
@@ -31,12 +46,12 @@ function getWsReqInfo(wsReq) {
// TODO 如果是windows机器,url是不是全路径?需要对其过滤,取出
const path = wsReq.url || '/';
- const isEncript = true && wsReq.connection && wsReq.connection.encrypted;
+ const isEncript = true && wsReq.connection && (wsReq.connection as any).encrypted;
/**
* construct the request headers based on original connection,
* but delete the `sec-websocket-*` headers as they are already consumed by AnyProxy
*/
- const getNoWsHeaders = () => {
+ const getNoWsHeaders = (): http.IncomingHttpHeaders => {
const originHeaders = Object.assign({}, headers);
const originHeaderKeys = Object.keys(originHeaders);
originHeaderKeys.forEach((key) => {
@@ -49,16 +64,15 @@ function getWsReqInfo(wsReq) {
delete originHeaders.connection;
delete originHeaders.upgrade;
return originHeaders;
- }
-
+ };
return {
- headers: headers, // the full headers of origin ws connection
+ headers, // the full headers of origin ws connection
noWsHeaders: getNoWsHeaders(),
- hostName: hostName,
- port: port,
- path: path,
- protocol: isEncript ? 'wss' : 'ws'
+ hostName,
+ port,
+ path,
+ protocol: isEncript ? 'wss' : 'ws',
};
}
/**
@@ -70,12 +84,13 @@ function getWsReqInfo(wsReq) {
* @param {object} httpsServerMgr
* @returns
*/
-function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
- const reqHandlerCtx = this; reqHandlerCtx.conns = new Map(); reqHandlerCtx.cltSockets = new Map()
+function getConnectReqHandler(userRule: AnyProxyRule, recorder: Recorder, httpsServerMgr: HttpsServerMgr)
+ : (req: http.IncomingMessage, socket: net.Socket, head: Buffer[]) => void {
+ const reqHandlerCtx = this; reqHandlerCtx.conns = new Map(); reqHandlerCtx.cltSockets = new Map();
- return function (req, cltSocket, head) {
- const host = req.url.split(':')[0],
- targetPort = req.url.split(':')[1];
+ return function(req: http.IncomingMessage, cltSocket: net.Socket, head: Buffer[]): void {
+ const host = req.url.split(':')[0];
+ const targetPort = req.url.split(':')[1];
let shouldIntercept;
let interceptWsRequest = false;
let requestDetail;
@@ -90,12 +105,12 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
4.1 if (websocket || do_not_intercept) --> pipe to target server
4.2 else --> pipe to local server and do man-in-the-middle attack
*/
- co(function *() {
+ co(function *(): Generator {
// determine whether to use the man-in-the-middle server
logUtil.printLog(color.green('received https CONNECT request ' + host));
requestDetail = {
host: req.url,
- _req: req
+ _req: req,
};
// the return value in default rule is null
// so if the value is null, will take it as final value
@@ -106,14 +121,14 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
shouldIntercept = reqHandlerCtx.forceProxyHttps;
}
})
- .then(() =>
- new Promise((resolve) => {
+ .then(() => {
+ return new Promise((resolve) => {
// mark socket connection as established, to detect the request protocol
cltSocket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', resolve);
- })
- )
- .then(() =>
- new Promise((resolve, reject) => {
+ });
+ })
+ .then(() => {
+ return new Promise((resolve, reject) => {
let resolved = false;
cltSocket.on('data', (chunk) => {
requestStream.push(chunk);
@@ -139,8 +154,8 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
cltSocket.on('end', () => {
requestStream.push(null);
});
- })
- )
+ });
+ })
.then((result) => {
// log and recorder
if (shouldIntercept) {
@@ -149,7 +164,7 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
logUtil.printLog('will bypass the man-in-the-middle proxy');
}
- //record
+ // record
if (recorder) {
resourceInfo = {
host,
@@ -157,7 +172,7 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
path: '',
url: 'https://' + host,
req,
- startTime: new Date().getTime()
+ startTime: new Date().getTime(),
};
resourceInfoId = recorder.appendRecord(resourceInfo);
}
@@ -168,18 +183,19 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
// server info from the original request
const originServer = {
host,
- port: (targetPort === 80) ? 443 : targetPort
- }
+ port: (targetPort === '80') ? 443 : targetPort,
+ };
const localHttpServer = {
host: 'localhost',
- port: reqHandlerCtx.httpServerPort
- }
+ port: reqHandlerCtx.httpServerPort,
+ };
// for ws request, redirect them to local ws server
return interceptWsRequest ? localHttpServer : originServer;
} else {
- return httpsServerMgr.getSharedHttpsServer(host).then(serverInfo => ({ host: serverInfo.host, port: serverInfo.port }));
+ return httpsServerMgr.getSharedHttpsServer(host)
+ .then((serverInfo) => ({ host: serverInfo.host, port: serverInfo.port }));
}
})
.then((serverInfo) => {
@@ -189,7 +205,7 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
return new Promise((resolve, reject) => {
const conn = net.connect(serverInfo.port, serverInfo.host, () => {
- //throttle for direct-foward https
+ // throttle for direct-foward https
if (global._throttle && !shouldIntercept) {
requestStream.pipe(conn);
conn.pipe(global._throttle.throttle()).pipe(cltSocket);
@@ -205,8 +221,8 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
reject(e);
});
- reqHandlerCtx.conns.set(serverInfo.host + ':' + serverInfo.port, conn)
- reqHandlerCtx.cltSockets.set(serverInfo.host + ':' + serverInfo.port, cltSocket)
+ reqHandlerCtx.conns.set(serverInfo.host + ':' + serverInfo.port, conn);
+ reqHandlerCtx.cltSockets.set(serverInfo.host + ':' + serverInfo.port, cltSocket);
});
})
.then(() => {
@@ -220,40 +236,40 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
recorder && recorder.updateRecord(resourceInfoId, resourceInfo);
}
})
- .catch(co.wrap(function *(error) {
+ .catch(co.wrap(function *(error: Error): Generator {
logUtil.printLog(util.collectErrorLog(error), logUtil.T_ERR);
try {
yield userRule.onConnectError(requestDetail, error);
- } catch (e) { }
+ } catch (e) { console.error(e); }
try {
let errorHeader = 'Proxy-Error: true\r\n';
errorHeader += 'Proxy-Error-Message: ' + (error || 'null') + '\r\n';
errorHeader += 'Content-Type: text/html\r\n';
cltSocket.write('HTTP/1.1 502\r\n' + errorHeader + '\r\n\r\n');
- } catch (e) { }
+ } catch (e) { console.error(e); }
}));
- }
+ };
}
/**
* get a websocket event handler
- @param @required {object} wsClient
+* @param @required {object} wsClient
*/
-function getWsHandler(userRule, recorder, wsClient, wsReq) {
+function getWsHandler(userRule: AnyProxyRule, recorder: Recorder, wsClient: WebSocket, wsReq: http.IncomingMessage): void {
const self = this;
try {
let resourceInfoId = -1;
- const resourceInfo = {
- wsMessages: [] // all ws messages go through AnyProxy
+ const resourceInfo: AnyProxyRecorder.ResourceInfo = {
+ wsMessages: [] as AnyProxyRecorder.WsResourceInfo[], // all ws messages go through AnyProxy
};
const clientMsgQueue = [];
const serverInfo = getWsReqInfo(wsReq);
const wsUrl = `${serverInfo.protocol}://${serverInfo.hostName}:${serverInfo.port}${serverInfo.path}`;
const proxyWs = new WebSocket(wsUrl, '', {
rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized,
- headers: serverInfo.noWsHeaders
+ headers: serverInfo.noWsHeaders as IWebsocketOptionHeaders,
});
if (recorder) {
@@ -263,7 +279,7 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
path: serverInfo.path,
url: wsUrl,
req: wsReq,
- startTime: new Date().getTime()
+ startTime: new Date().getTime(),
});
resourceInfoId = recorder.appendRecord(resourceInfo);
}
@@ -283,7 +299,7 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
} else {
clientMsgQueue.push(message);
}
- }
+ };
/**
* consume the message in queue when the proxy ws is not ready yet
@@ -294,7 +310,7 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
const message = clientMsgQueue.shift();
proxyWs.send(message);
}
- }
+ };
/**
* When the source ws is closed, we need to close the target websocket.
@@ -303,7 +319,7 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
const getCloseFromOriginEvent = (event) => {
const code = event.code || '';
const reason = event.reason || '';
- let targetCode = '';
+ let targetCode;
let targetReason = '';
if (code >= 1004 && code <= 1006) {
targetCode = 1000; // normal closure
@@ -315,9 +331,9 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
return {
code: targetCode,
- reason: targetReason
- }
- }
+ reason: targetReason,
+ };
+ };
/**
* consruct a message Record from message event
@@ -329,7 +345,7 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
const message = {
time: Date.now(),
message: messageEvent.data,
- isToServer: isToServer
+ isToServer,
};
// resourceInfo.wsMessages.push(message);
@@ -338,15 +354,15 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
proxyWs.onopen = () => {
consumeMsgQueue();
- }
+ };
// this event is fired when the connection is build and headers is returned
proxyWs.on('upgrade', (response) => {
resourceInfo.endTime = new Date().getTime();
const headers = response.headers;
- resourceInfo.res = { //construct a self-defined res object
+ resourceInfo.res = { // construct a self-defined res object
statusCode: response.statusCode,
- headers: headers,
+ headers,
};
resourceInfo.statusCode = response.statusCode;
@@ -361,29 +377,29 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
wsClient.close(1001, e.message);
proxyWs.close(1001);
- }
+ };
proxyWs.onmessage = (event) => {
recordMessage(event, false);
wsClient.readyState === 1 && wsClient.send(event.data);
- }
+ };
proxyWs.onclose = (event) => {
logUtil.debug(`proxy ws closed with code: ${event.code} and reason: ${event.reason}`);
const targetCloseInfo = getCloseFromOriginEvent(event);
wsClient.readyState !== 3 && wsClient.close(targetCloseInfo.code, targetCloseInfo.reason);
- }
+ };
wsClient.onmessage = (event) => {
recordMessage(event, true);
sendProxyMessage(event);
- }
+ };
wsClient.onclose = (event) => {
logUtil.debug(`original ws closed with code: ${event.code} and reason: ${event.reason}`);
const targetCloseInfo = getCloseFromOriginEvent(event);
proxyWs.readyState !== 3 && proxyWs.close(targetCloseInfo.code, targetCloseInfo.reason);
- }
+ };
} catch (e) {
logUtil.debug('WebSocket Proxy Error:' + e.message);
logUtil.debug(e.stack);
@@ -392,7 +408,14 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
}
class RequestHandler {
-
+ public forceProxyHttps: boolean;
+ public dangerouslyIgnoreUnauthorized: boolean;
+ public httpServerPort: string;
+ public wsIntercept: boolean;
+ public connectReqHandler: () => void;
+ private userRequestHandler: () => void;
+ private wsHandler: () => void;
+ private httpsServerMgr: HttpsServerMgr;
/**
* Creates an instance of RequestHandler.
*
@@ -405,7 +428,7 @@ class RequestHandler {
*
* @memberOf RequestHandler
*/
- constructor(config, rule, recorder) {
+ constructor(config: AnyProxyConfig, rule: AnyProxyRule, recorder: Recorder) {
const reqHandlerCtx = this;
this.forceProxyHttps = false;
this.dangerouslyIgnoreUnauthorized = false;
@@ -425,8 +448,8 @@ class RequestHandler {
}
this.httpServerPort = config.httpServerPort;
- const default_rule = util.freshRequire('./rule_default');
- const userRule = util.merge(default_rule, rule);
+ const defaultRule = util.freshRequire('./rule_default');
+ const userRule = util.merge(defaultRule, rule);
const userReqHandler = new UserReqHandler(reqHandlerCtx, userRule, recorder);
reqHandlerCtx.userRequestHandler = userReqHandler.handler.bind(userReqHandler);
@@ -434,11 +457,11 @@ class RequestHandler {
reqHandlerCtx.httpsServerMgr = new HttpsServerMgr({
handler: reqHandlerCtx.userRequestHandler,
- wsHandler: reqHandlerCtx.wsHandler // websocket
+ wsHandler: reqHandlerCtx.wsHandler, // websocket
});
this.connectReqHandler = getConnectReqHandler.apply(reqHandlerCtx, [userRule, recorder, reqHandlerCtx.httpsServerMgr]);
}
}
-module.exports = RequestHandler;
+export default RequestHandler;
diff --git a/lib/requestHandler/requestErrorHandler.js b/lib/requestHandler/requestErrorHandler.ts
similarity index 52%
rename from lib/requestHandler/requestErrorHandler.js
rename to lib/requestHandler/requestErrorHandler.ts
index 8fd9f9e..4f06997 100644
--- a/lib/requestHandler/requestErrorHandler.js
+++ b/lib/requestHandler/requestErrorHandler.ts
@@ -4,8 +4,8 @@
* handle all request error here,
*
*/
-const pug = require('pug');
-const path = require('path');
+import * as pug from 'pug';
+import * as path from 'path';
const error502PugFn = pug.compileFile(path.join(__dirname, '../resource/502.pug'));
const certPugFn = pug.compileFile(path.join(__dirname, '../resource/cert_error.pug'));
@@ -13,7 +13,7 @@ const certPugFn = pug.compileFile(path.join(__dirname, '../resource/cert_error.p
/**
* get error content for certification issues
*/
-function getCertErrorContent(error, fullUrl) {
+function getCertErrorContent(error: NodeJS.ErrnoException, fullUrl: string): string {
let content;
const title = 'The connection is not private. ';
let explain = 'There are error with the certfication of the site.';
@@ -21,21 +21,22 @@ function getCertErrorContent(error, fullUrl) {
case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
explain = 'The certfication of the site you are visiting is not issued by a known agency, '
+ 'It usually happenes when the cert is a self-signed one.'
- + 'If you know and trust the site, you can run AnyProxy with option -ignore-unauthorized-ssl to continue.'
+ + 'If you know and trust the site, you can run AnyProxy with option'
+ + ' -ignore-unauthorized-ssl to continue.'
break;
}
default: {
- explain = ''
+ explain = '';
break;
}
}
try {
content = certPugFn({
- title: title,
- explain: explain,
- code: error.code
+ title,
+ explain,
+ code: error.code,
});
} catch (parseErro) {
content = error.stack;
@@ -47,14 +48,14 @@ function getCertErrorContent(error, fullUrl) {
/*
* get the default error content
*/
-function getDefaultErrorCotent(error, fullUrl) {
+function getDefaultErrorCotent(error: NodeJS.ErrnoException, fullUrl: string): string {
let content;
try {
content = error502PugFn({
error,
url: fullUrl,
- errorStack: error.stack.split(/\n/)
+ errorStack: error.stack.split(/\n/),
});
} catch (parseErro) {
content = error.stack;
@@ -66,19 +67,22 @@ function getDefaultErrorCotent(error, fullUrl) {
/*
* get mapped error content for each error
*/
-module.exports.getErrorContent = function (error, fullUrl) {
- let content = '';
- error = error || {};
- switch (error.code) {
- case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
- content = getCertErrorContent(error, fullUrl);
- break;
- }
- default: {
- content = getDefaultErrorCotent(error, fullUrl);
- break;
- }
- }
+export default class RequestErrorHandler {
+ public getErrorContent: (error: NodeJS.ErrnoException, fullUrl: string) => string =
+ function(error: NodeJS.ErrnoException, fullUrl: string): string {
+ let content = '';
+ error = error || { name: 'default_by_anyproxy', message: 'this is the default error'};
+ switch (error.code) {
+ case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
+ content = getCertErrorContent(error, fullUrl);
+ break;
+ }
+ default: {
+ content = getDefaultErrorCotent(error, fullUrl);
+ break;
+ }
+ }
- return content;
+ return content;
+ };
}
diff --git a/lib/ruleLoader.js b/lib/ruleLoader.js
index 1d498ff..8ec9037 100644
--- a/lib/ruleLoader.js
+++ b/lib/ruleLoader.js
@@ -1,6 +1,6 @@
'use strict';
-const proxyUtil = require('./util');
+const proxyUtil = require('./util').default;
const path = require('path');
const fs = require('fs');
const request = require('request');
diff --git a/lib/util.ts b/lib/util.ts
index ce34729..5f15d97 100644
--- a/lib/util.ts
+++ b/lib/util.ts
@@ -17,330 +17,330 @@ import { execSync } from 'child_process';
import logUtil from './log';
- const networkInterfaces = require('os').networkInterfaces();
+const networkInterfaces = require('os').networkInterfaces();
- // {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
- function lower_keys (obj: object): object {
- for (const key in obj) {
- const val = obj[key];
- delete obj[key];
+// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
+function lower_keys (obj: object): object {
+ for (const key in obj) {
+ const val = obj[key];
+ delete obj[key];
- obj[key.toLowerCase()] = val;
- }
-
- return obj;
- };
-
- function merge (baseObj: object, extendObj: object): object {
- for (const key in extendObj) {
- baseObj[key] = extendObj[key];
- }
-
- return baseObj;
- };
-
- function getUserHome(): string {
- return process.env.HOME || process.env.USERPROFILE;
+ obj[key.toLowerCase()] = val;
}
- function getAnyProxyHome(): string {
- const home = path.join(getUserHome(), '/.anyproxy/');
- if (!fs.existsSync(home)) {
- fs.mkdirSync(home);
- }
- return home;
+ return obj;
+};
+
+function merge (baseObj: object, extendObj: object): object {
+ for (const key in extendObj) {
+ baseObj[key] = extendObj[key];
}
- function getAnyProxyPath (pathName): string {
- const home = getAnyProxyHome();
- const targetPath = path.join(home, pathName);
- if (!fs.existsSync(targetPath)) {
- fs.mkdirSync(targetPath);
- }
- return targetPath;
+ return baseObj;
+};
+
+function getUserHome(): string {
+ return process.env.HOME || process.env.USERPROFILE;
+}
+
+function getAnyProxyHome(): string {
+ const home = path.join(getUserHome(), '/.anyproxy/');
+ if (!fs.existsSync(home)) {
+ fs.mkdirSync(home);
}
+ return home;
+}
- /**
- * 简易字符串render替换
- */
- function simpleRender (str: string, object: object, regexp: RegExp) {
- return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
- if (match.charAt(0) === '\\') {
- return match.slice(1);
- }
- return (object[name] != null) ? object[name] : '';
- });
- };
+function getAnyProxyPath (pathName): string {
+ const home = getAnyProxyHome();
+ const targetPath = path.join(home, pathName);
+ if (!fs.existsSync(targetPath)) {
+ fs.mkdirSync(targetPath);
+ }
+ return targetPath;
+}
- /**
- * 读取指定目录下的子目录
- */
- function filewalker (root: string, cb: Function) {
- root = root || process.cwd();
-
- const ret = {
- directory: [],
- file: []
- };
-
- fs.readdir(root, (err, list) => {
- if (list && list.length) {
- list.map((item) => {
- const fullPath = path.join(root, item),
- stat = fs.lstatSync(fullPath);
-
- if (stat.isFile()) {
- ret.file.push({
- name: item,
- fullPath
- });
- } else if (stat.isDirectory()) {
- ret.directory.push({
- name: item,
- fullPath
- });
- }
- });
- }
-
- cb && cb.apply(null, [null, ret]);
- });
- };
-
- /*
- * 获取文件所对应的content-type以及content-length等信息
- * 比如在useLocalResponse的时候会使用到
- */
- function contentType (filepath: string): string {
- return mime.contentType(path.extname(filepath)) || '';
- };
-
- /*
- * 读取file的大小,以byte为单位
- */
- function contentLength (filepath: string): number {
- try {
- const 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;
+/**
+ * 简易字符串render替换
+ */
+function simpleRender (str: string, object: object, regexp: RegExp) {
+ return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
+ if (match.charAt(0) === '\\') {
+ return match.slice(1);
}
+ return (object[name] != null) ? object[name] : '';
+ });
+};
+
+/**
+ * 读取指定目录下的子目录
+ */
+function filewalker (root: string, cb: Function) {
+ root = root || process.cwd();
+
+ const ret = {
+ directory: [],
+ file: []
};
- /*
- * remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS
- */
- function freshRequire (modulePath: string): NodeModule {
- delete require.cache[require.resolve(modulePath)];
- return require(modulePath);
- };
+ fs.readdir(root, (err, list) => {
+ if (list && list.length) {
+ list.map((item) => {
+ const fullPath = path.join(root, item),
+ stat = fs.lstatSync(fullPath);
- /*
- * format the date string
- * @param date Date or timestamp
- * @param formatter YYYYMMDDHHmmss
- */
- function formatDate (date: Date | number, formatter: string): string {
- let finalDate : Date;
- if (typeof date !== 'object') {
- finalDate = new Date(date);
+ if (stat.isFile()) {
+ ret.file.push({
+ name: item,
+ fullPath
+ });
+ } else if (stat.isDirectory()) {
+ ret.directory.push({
+ name: item,
+ fullPath
+ });
+ }
+ });
+ }
+
+ cb && cb.apply(null, [null, ret]);
+ });
+};
+
+/*
+* 获取文件所对应的content-type以及content-length等信息
+* 比如在useLocalResponse的时候会使用到
+*/
+function contentType (filepath: string): string {
+ return mime.contentType(path.extname(filepath)) || '';
+};
+
+/*
+* 读取file的大小,以byte为单位
+*/
+function contentLength (filepath: string): number {
+ try {
+ const 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;
+ }
+};
+
+/*
+* remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS
+*/
+function freshRequire (modulePath: string): NodeModule {
+ delete require.cache[require.resolve(modulePath)];
+ return require(modulePath);
+};
+
+/*
+* format the date string
+* @param date Date or timestamp
+* @param formatter YYYYMMDDHHmmss
+*/
+function formatDate (date: Date | number, formatter: string): string {
+ let finalDate : Date;
+ if (typeof date !== 'object') {
+ finalDate = new Date(date);
+ } else {
+ finalDate = date;
+ }
+ const transform = function (value) {
+ return value < 10 ? '0' + value : value;
+ };
+ return formatter.replace(/^YYYY|MM|DD|hh|mm|ss/g, (match) => {
+ switch (match) {
+ case 'YYYY':
+ return transform(finalDate.getFullYear());
+ case 'MM':
+ return transform(finalDate.getMonth() + 1);
+ case 'mm':
+ return transform(finalDate.getMinutes());
+ case 'DD':
+ return transform(finalDate.getDate());
+ case 'hh':
+ return transform(finalDate.getHours());
+ case 'ss':
+ return transform(finalDate.getSeconds());
+ default:
+ return ''
+ }
+ });
+};
+
+
+/**
+* get headers(Object) from rawHeaders(Array)
+* @param rawHeaders [key, value, key2, value2, ...]
+
+*/
+
+function getHeaderFromRawHeaders (rawHeaders: Array) {
+ const headerObj = {};
+ const _handleSetCookieHeader = function (key, value) {
+ if (headerObj[key].constructor === Array) {
+ headerObj[key].push(value);
} else {
- finalDate = date;
+ headerObj[key] = [headerObj[key], value];
}
- const transform = function (value) {
- return value < 10 ? '0' + value : value;
- };
- return formatter.replace(/^YYYY|MM|DD|hh|mm|ss/g, (match) => {
- switch (match) {
- case 'YYYY':
- return transform(finalDate.getFullYear());
- case 'MM':
- return transform(finalDate.getMonth() + 1);
- case 'mm':
- return transform(finalDate.getMinutes());
- case 'DD':
- return transform(finalDate.getDate());
- case 'hh':
- return transform(finalDate.getHours());
- case 'ss':
- return transform(finalDate.getSeconds());
- default:
- return ''
- }
- });
};
+ if (!!rawHeaders) {
+ for (let i = 0; i < rawHeaders.length; i += 2) {
+ const key = rawHeaders[i];
+ let value = rawHeaders[i + 1];
- /**
- * get headers(Object) from rawHeaders(Array)
- * @param rawHeaders [key, value, key2, value2, ...]
+ if (typeof value === 'string') {
+ value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串
+ }
- */
-
- function getHeaderFromRawHeaders (rawHeaders: Array) {
- const headerObj = {};
- const _handleSetCookieHeader = function (key, value) {
- if (headerObj[key].constructor === Array) {
- headerObj[key].push(value);
+ if (!headerObj[key]) {
+ headerObj[key] = value;
} else {
- headerObj[key] = [headerObj[key], value];
- }
- };
-
- if (!!rawHeaders) {
- for (let i = 0; i < rawHeaders.length; i += 2) {
- const key = rawHeaders[i];
- let value = rawHeaders[i + 1];
-
- if (typeof value === 'string') {
- value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串
- }
-
- if (!headerObj[key]) {
- headerObj[key] = value;
+ // headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+ // set-cookie should NOT be combined. Ref: https://tools.ietf.org/html/rfc6265
+ if (key.toLowerCase() === 'set-cookie') {
+ _handleSetCookieHeader(key, value);
} else {
- // headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
- // set-cookie should NOT be combined. Ref: https://tools.ietf.org/html/rfc6265
- if (key.toLowerCase() === 'set-cookie') {
- _handleSetCookieHeader(key, value);
- } else {
- headerObj[key] = headerObj[key] + ',' + value;
- }
+ headerObj[key] = headerObj[key] + ',' + value;
}
}
}
- return headerObj;
- };
+ }
+ return headerObj;
+};
- function getAllIpAddress (): Array {
- const allIp = [];
+function getAllIpAddress (): Array {
+ const allIp = [];
- Object.keys(networkInterfaces).map((nic) => {
- networkInterfaces[nic].filter((detail) => {
- if (detail.family.toLowerCase() === 'ipv4') {
- allIp.push(detail.address);
- }
- });
+ Object.keys(networkInterfaces).map((nic) => {
+ networkInterfaces[nic].filter((detail) => {
+ if (detail.family.toLowerCase() === 'ipv4') {
+ allIp.push(detail.address);
+ }
+ });
+ });
+
+ return allIp.length ? allIp : ['127.0.0.1'];
+};
+
+function deleteFolderContentsRecursive(dirPath: string, ifClearFolderItself: boolean): void {
+ if (!dirPath.trim() || dirPath === '/') {
+ throw new Error('can_not_delete_this_dir');
+ }
+
+ if (fs.existsSync(dirPath)) {
+ fs.readdirSync(dirPath).forEach((file) => {
+ const curPath = path.join(dirPath, file);
+ if (fs.lstatSync(curPath).isDirectory()) {
+ deleteFolderContentsRecursive(curPath, true);
+ } else { // delete all files
+ fs.unlinkSync(curPath);
+ }
});
- return allIp.length ? allIp : ['127.0.0.1'];
- };
-
- function deleteFolderContentsRecursive(dirPath: string, ifClearFolderItself: boolean): void {
- if (!dirPath.trim() || dirPath === '/') {
- throw new Error('can_not_delete_this_dir');
- }
-
- if (fs.existsSync(dirPath)) {
- fs.readdirSync(dirPath).forEach((file) => {
- const curPath = path.join(dirPath, file);
- if (fs.lstatSync(curPath).isDirectory()) {
- deleteFolderContentsRecursive(curPath, true);
- } else { // delete all files
- fs.unlinkSync(curPath);
- }
- });
-
- if (ifClearFolderItself) {
- try {
- // ref: https://github.com/shelljs/shelljs/issues/49
- const start = Date.now();
- while (true) {
- try {
- fs.rmdirSync(dirPath);
+ if (ifClearFolderItself) {
+ try {
+ // ref: https://github.com/shelljs/shelljs/issues/49
+ const start = Date.now();
+ while (true) {
+ try {
+ fs.rmdirSync(dirPath);
+ break;
+ } catch (er) {
+ if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) {
+ // Retry on windows, sometimes it takes a little time before all the files in the directory are gone
+ if (Date.now() - start > 1000) throw er;
+ } else if (er.code === 'ENOENT') {
break;
- } catch (er) {
- if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) {
- // Retry on windows, sometimes it takes a little time before all the files in the directory are gone
- if (Date.now() - start > 1000) throw er;
- } else if (er.code === 'ENOENT') {
- break;
- } else {
- throw er;
- }
+ } else {
+ throw er;
}
}
- } catch (e) {
- throw new Error('could not remove directory (code ' + e.code + '): ' + dirPath);
}
+ } catch (e) {
+ throw new Error('could not remove directory (code ' + e.code + '): ' + dirPath);
}
}
}
+}
- function getFreePort (): Promise {
- return new Promise((resolve, reject) => {
- const server = require('net').createServer();
- server.unref();
- server.on('error', reject);
- server.listen(0, () => {
- const port = server.address().port;
- server.close(() => {
- resolve(port);
- });
+function getFreePort (): Promise {
+ return new Promise((resolve, reject) => {
+ const server = require('net').createServer();
+ server.unref();
+ server.on('error', reject);
+ server.listen(0, () => {
+ const port = server.address().port;
+ server.close(() => {
+ resolve(port);
});
});
- }
+ });
+}
- function collectErrorLog (error: any): string {
- if (error && error.code && error.toString()) {
- return error.toString();
- } else {
- let result = [error, error.stack].join('\n');
- try {
- const errorString = error.toString();
- if (errorString.indexOf('You may only yield a function') >= 0) {
- result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq';
- }
- } catch (e) {}
- return result
- }
- }
-
- function isFunc (source: object): boolean {
- return source && Object.prototype.toString.call(source) === '[object Function]';
- };
-
- /**
- * @param {object} content
- * @returns the size of the content
- */
- function getByteSize (content: Buffer): number {
- return Buffer.byteLength(content);
- };
-
- /*
- * identify whether the
- */
- function isIpDomain (domain: string): boolean {
- if (!domain) {
- return false;
- }
- const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/;
-
- return ipReg.test(domain);
- };
-
- function execScriptSync (cmd: string): object {
- let stdout,
- status = 0;
+function collectErrorLog (error: any): string {
+ if (error && error.code && error.toString()) {
+ return error.toString();
+ } else {
+ let result = [error, error.stack].join('\n');
try {
- stdout = execSync(cmd);
- } catch (err) {
- stdout = err.stdout;
- status = err.status;
- }
+ const errorString = error.toString();
+ if (errorString.indexOf('You may only yield a function') >= 0) {
+ result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq';
+ }
+ } catch (e) {}
+ return result
+ }
+}
- return {
- stdout: stdout.toString(),
- status
- };
- };
+function isFunc (source: object): boolean {
+ return source && Object.prototype.toString.call(source) === '[object Function]';
+};
- function guideToHomePage (): void {
- logUtil.info('Refer to http://anyproxy.io for more detail');
+/**
+* @param {object} content
+* @returns the size of the content
+*/
+function getByteSize (content: Buffer): number {
+ return Buffer.byteLength(content);
+};
+
+/*
+* identify whether the
+*/
+function isIpDomain (domain: string): boolean {
+ if (!domain) {
+ return false;
+ }
+ const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/;
+
+ return ipReg.test(domain);
+};
+
+function execScriptSync (cmd: string): object {
+ let stdout,
+ status = 0;
+ try {
+ stdout = execSync(cmd);
+ } catch (err) {
+ stdout = err.stdout;
+ status = err.status;
+ }
+
+ return {
+ stdout: stdout.toString(),
+ status
};
+};
+
+function guideToHomePage (): void {
+ logUtil.info('Refer to http://anyproxy.io for more detail');
+};
const Util = {
@@ -364,9 +364,9 @@ const Util = {
deleteFolderContentsRecursive,
execScriptSync,
guideToHomePage,
- formatDate
-}
+ formatDate,
+};
export default Util;
-module.exports = Util;
-// })();
\ No newline at end of file
+
+
diff --git a/lib/webInterface.js b/lib/webInterface.js
index b039fc9..003da6d 100644
--- a/lib/webInterface.js
+++ b/lib/webInterface.js
@@ -9,7 +9,7 @@ const express = require('express'),
path = require('path'),
events = require('events'),
qrCode = require('qrcode-npm'),
- util = require('./util'),
+ util = require('./util').default,
certMgr = require('./certMgr'),
wsServer = require('./wsServer'),
juicer = require('juicer'),
@@ -56,7 +56,7 @@ class webInterface extends events.EventEmitter {
*/
getServer() {
const self = this;
- const recorder = self.recorder;
+ const recorder = self.recorder;
const ipAddress = ip.address(),
// userRule = proxyInstance.proxyRule,
webBasePath = 'web';
diff --git a/package.json b/package.json
index 2865e1d..c17c4c4 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
},
"devDependencies": {
"@types/node": "^8.10.21",
+ "@types/ws": "^6.0.0",
"antd": "^2.5.0",
"autoprefixer": "^6.4.1",
"babel-core": "^6.14.0",
diff --git a/rule_sample/sample_modify_request_path.js b/rule_sample/sample_modify_request_path.js
index 1df4aca..f72e5d7 100644
--- a/rule_sample/sample_modify_request_path.js
+++ b/rule_sample/sample_modify_request_path.js
@@ -9,14 +9,19 @@
module.exports = {
*beforeSendRequest(requestDetail) {
if (requestDetail.url.indexOf('https://httpbin.org/user-agent') === 0) {
+ console.info(requestDetail._req.connection._peername);
const newRequestOptions = requestDetail.requestOptions;
- requestDetail.protocol = 'http';
+ requestDetail.protocol = 'https';
newRequestOptions.hostname = '127.0.0.1'
- newRequestOptions.port = '8008';
- newRequestOptions.path = '/index.html';
+ newRequestOptions.port = '3001';
+ newRequestOptions.path = '/test';
newRequestOptions.method = 'GET';
return requestDetail;
}
+
+ if (requestDetail.url.indexOf('http://mobilegw.stable.alipay.net/mgw.htm') === 0) {
+ console.info(requestDetail.requestData.toString())
+ }
},
*beforeDealHttpsRequest(requestDetail) {
return true;
diff --git a/test.js b/test.js
index abd6d8c..9698d85 100644
--- a/test.js
+++ b/test.js
@@ -1,7 +1,7 @@
const Jasmine = require('jasmine');
const jasmine = new Jasmine();
-const util = require('./lib/util');
+const util = require('./lib/util').default;
const path = require('path');
const testTmpPath = path.join(__dirname, './test/temp');
diff --git a/test/spec_lib/util.js b/test/spec_lib/util.js
index 8881df3..ec31cf3 100644
--- a/test/spec_lib/util.js
+++ b/test/spec_lib/util.js
@@ -2,7 +2,7 @@
* test for rule replaceOption rule
*
*/
-const util = require('../../lib/util');
+const util = require('../../lib/util').default;
describe('utils', () => {
it('should get some free ports', done => {
diff --git a/tsconfig.json b/tsconfig.json
index 7e454fc..da6c1a0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,7 +16,7 @@
"./dist"
],
"include": [
- "./lib/*",
- "./bin/*"
+ "./lib/**/*",
+ "./bin/**/*"
]
}
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000..6635f17
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,48 @@
+{
+ "extends": "tslint:recommended",
+ "rulesDirectory": [
+ ],
+ "tslint.options": {
+ "project": "tsconfig.json",
+ "typeCheck": true
+ },
+ "rules": {
+ "jsdoc-format": false,
+ "object-literal-sort-keys": false,
+ "quotemark": [true, "single"],
+ "ordered-imports": false,
+ "only-arrow-functions": false,
+ "no-reference": false,
+ "typedef": [
+ true,
+ "call-signature",
+ "parameter",
+ "member-variable-declaration"
+ ],
+ "max-line-length": {
+ "options": [
+ 140
+ ]
+ },
+ "new-parens": true,
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-conditional-assignment": true,
+ "no-consecutive-blank-lines": false,
+ "no-unused-expression": [
+ true,
+ "allow-fast-null-checks"
+ ],
+ "no-console": {
+ "severity": "warning",
+ "options": [
+ "debug",
+ "info",
+ "log",
+ "time",
+ "timeEnd",
+ "trace"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/typings/index.d.ts b/typings/index.d.ts
index fe83544..12a6a93 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -1,6 +1,21 @@
-declare interface AnyProxyConfig {
- port: number
+
+///
+
+declare module NodeJS {
+ interface Global {
+ _throttle: any;
+ }
}
+
+declare interface AnyProxyConfig {
+ port: string; // 代理监听端口
+ httpServerPort: string; // web server 的端口
+ forceProxyHttps: boolean;
+ dangerouslyIgnoreUnauthorized: boolean; // 是否忽略https证书
+ wsIntercept: boolean; // 是否代理websocket
+ chunkSizeThreshold: number; // throttle的配置
+}
+
declare interface AnyProxyRule {
summary?: string,
beforeSendRequest?: Function,
@@ -8,4 +23,59 @@ declare interface AnyProxyRule {
beforeDealHttpsRequest?: Function,
onError?: Function,
onConnectError?: Function
+}
+
+declare namespace AnyProxyRecorder {
+ type ResponseHeader = any; // 暂时无法引入http模块,会导致
+ export type WsResourceInfo = {
+ time: number,
+ message: string,
+ isToServer: boolean
+ };
+
+ export type ResourceInfo = {
+ wsMessages?: Array,
+ statusCode?: number,
+ resHeader?: ResponseHeader,
+ host?: string,
+ method?: string,
+ path?: string,
+ url?: string,
+ startTime?: number,
+ endTime?: number,
+ res?: {
+ statusCode: number,
+ headers: ResponseHeader
+ },
+ resBody?: string,
+ length?: number
+ };
+}
+
+// The request detail object AnyProxy used internally
+declare interface AnyProxyRequestDetail {
+ protocol?: string;
+ requestOptions?: any;
+ requestData?: Buffer;
+ url?: string;
+ _req?: any;
+ _directlyPassToRespond?: boolean;
+ response?: AnyProxyResponse; // exists when gen the response directly from request
+}
+
+declare interface AnyProxyResponse {
+ statusCode: number,
+ header: OneLevelObjectType;
+ body: string | Buffer;
+ rawBody: Buffer;
+}
+
+declare interface AnyProxyReponseDetail {
+ response: AnyProxyResponse,
+ _res: any;
+ _directlyPassToRespond?: boolean; // no need to send the respnose out
+}
+
+declare interface OneLevelObjectType {
+ [key: string]: string | boolean | number
}
\ No newline at end of file