mirror of
https://github.com/alibaba/anyproxy.git
synced 2025-05-11 07:28:59 +00:00
feat: refact requestHandler into TS
This commit is contained in:
parent
51abd0532c
commit
201447a19a
@ -6,7 +6,7 @@ const program = require('commander'),
|
|||||||
color = require('colorful'),
|
color = require('colorful'),
|
||||||
co = require('co'),
|
co = require('co'),
|
||||||
packageInfo = require('../package.json'),
|
packageInfo = require('../package.json'),
|
||||||
util = require('../dist/util'),
|
util = require('../dist/util').default,
|
||||||
rootCACheck = require('./rootCaCheck'),
|
rootCACheck = require('./rootCaCheck'),
|
||||||
startServer = require('./startServer'),
|
startServer = require('./startServer'),
|
||||||
certMgr = require('../dist/certMgr'),
|
certMgr = require('../dist/certMgr'),
|
||||||
|
@ -5,7 +5,7 @@ const co = require('co');
|
|||||||
const os = require('os');
|
const os = require('os');
|
||||||
const inquirer = require('inquirer');
|
const inquirer = require('inquirer');
|
||||||
|
|
||||||
const util = require('./util');
|
const util = require('./util').default;
|
||||||
const logUtil = require('./log');
|
const logUtil = require('./log');
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -8,7 +8,7 @@ const async = require('async'),
|
|||||||
color = require('colorful'),
|
color = require('colorful'),
|
||||||
certMgr = require('./certMgr'),
|
certMgr = require('./certMgr'),
|
||||||
logUtil = require('./log'),
|
logUtil = require('./log'),
|
||||||
util = require('./util'),
|
util = require('./util').default,
|
||||||
wsServerMgr = require('./wsServerMgr'),
|
wsServerMgr = require('./wsServerMgr'),
|
||||||
co = require('co'),
|
co = require('co'),
|
||||||
constants = require('constants'),
|
constants = require('constants'),
|
||||||
@ -194,4 +194,4 @@ class httpsServerMgr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = httpsServerMgr;
|
export default httpsServerMgr;
|
||||||
|
172
lib/log.ts
172
lib/log.ts
@ -1,109 +1,109 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import * as color from 'colorful';
|
import * as color from 'colorful';
|
||||||
import util from './util';
|
import util from './util';
|
||||||
|
|
||||||
let ifPrint = true;
|
let ifPrint = true;
|
||||||
let logLevel = 0;
|
let logLevel = 0;
|
||||||
enum LogLevelMap {
|
enum LogLevelMap {
|
||||||
tip = 0,
|
tip = 0,
|
||||||
system_error = 1,
|
system_error = 1,
|
||||||
error = 1,
|
error = 1,
|
||||||
rule_error = 2,
|
rule_error = 2,
|
||||||
warn = 3,
|
warn = 3,
|
||||||
debug = 4,
|
debug = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
function setPrintStatus(status: boolean): void {
|
function setPrintStatus(status: boolean): void {
|
||||||
ifPrint = !!status;
|
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 {
|
const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss');
|
||||||
logLevel = parseInt(level, 10);
|
switch (type) {
|
||||||
}
|
case LogLevelMap.tip: {
|
||||||
|
if (logLevel > 0) {
|
||||||
function printLog(content: string, type?: LogLevelMap) {
|
return;
|
||||||
if (!ifPrint) {
|
}
|
||||||
return;
|
console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss');
|
case LogLevelMap.system_error: {
|
||||||
switch (type) {
|
if (logLevel > 1) {
|
||||||
case LogLevelMap.tip: {
|
return;
|
||||||
if (logLevel > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
console.error(color.red(`[AnyProxy ERROR][${timeString}]: ` + content));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case LogLevelMap.system_error: {
|
case LogLevelMap.rule_error: {
|
||||||
if (logLevel > 1) {
|
if (logLevel > 2) {
|
||||||
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));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
console.error(color.red(`[AnyProxy RULE_ERROR][${timeString}]: ` + content));
|
||||||
console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
|
break;
|
||||||
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 {
|
function debug (content): void {
|
||||||
printLog(content, LogLevelMap.debug);
|
printLog(content, LogLevelMap.debug);
|
||||||
};
|
};
|
||||||
|
|
||||||
function info (content): void {
|
function info (content): void {
|
||||||
printLog(content, LogLevelMap.tip);
|
printLog(content, LogLevelMap.tip);
|
||||||
};
|
};
|
||||||
|
|
||||||
function warn (content) {
|
function warn (content) {
|
||||||
printLog(content, LogLevelMap.warn);
|
printLog(content, LogLevelMap.warn);
|
||||||
};
|
};
|
||||||
|
|
||||||
function error (content) {
|
function error (content) {
|
||||||
printLog(content, LogLevelMap.system_error);
|
printLog(content, LogLevelMap.system_error);
|
||||||
};
|
};
|
||||||
|
|
||||||
function ruleError (content) {
|
function ruleError (content) {
|
||||||
printLog(content, LogLevelMap.rule_error);
|
printLog(content, LogLevelMap.rule_error);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.setPrintStatus = setPrintStatus;
|
module.exports.setPrintStatus = setPrintStatus;
|
||||||
module.exports.setLogLevel = setLogLevel;
|
module.exports.setLogLevel = setLogLevel;
|
||||||
module.exports.T_TIP = LogLevelMap.tip;
|
module.exports.T_TIP = LogLevelMap.tip;
|
||||||
module.exports.T_ERR = LogLevelMap.system_error;
|
module.exports.T_ERR = LogLevelMap.system_error;
|
||||||
module.exports.T_RULE_ERROR = LogLevelMap.rule_error;
|
module.exports.T_RULE_ERROR = LogLevelMap.rule_error;
|
||||||
module.exports.T_WARN = LogLevelMap.warn;
|
module.exports.T_WARN = LogLevelMap.warn;
|
||||||
module.exports.T_DEBUG = LogLevelMap.debug;
|
module.exports.T_DEBUG = LogLevelMap.debug;
|
||||||
|
|
||||||
const LogUtil = {
|
const LogUtil = {
|
||||||
setPrintStatus,
|
setPrintStatus,
|
||||||
@ -118,8 +118,8 @@ const LogUtil = {
|
|||||||
T_ERR: LogLevelMap.error,
|
T_ERR: LogLevelMap.error,
|
||||||
T_RULE_ERROR: LogLevelMap.rule_error,
|
T_RULE_ERROR: LogLevelMap.rule_error,
|
||||||
T_WARN: LogLevelMap.warn,
|
T_WARN: LogLevelMap.warn,
|
||||||
T_DEBUG: LogLevelMap.debug
|
T_DEBUG: LogLevelMap.debug,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
exports.LogUtil = LogUtil;
|
||||||
export default LogUtil;
|
export default LogUtil;
|
||||||
module.exports = LogUtil;
|
|
||||||
|
@ -7,7 +7,7 @@ const http = require('http'),
|
|||||||
certMgr = require('./certMgr'),
|
certMgr = require('./certMgr'),
|
||||||
Recorder = require('./recorder'),
|
Recorder = require('./recorder'),
|
||||||
logUtil = require('./log'),
|
logUtil = require('./log'),
|
||||||
util = require('./util'),
|
util = require('./util').default,
|
||||||
events = require('events'),
|
events = require('events'),
|
||||||
co = require('co'),
|
co = require('co'),
|
||||||
WebInterface = require('./webInterface'),
|
WebInterface = require('./webInterface'),
|
||||||
@ -114,7 +114,7 @@ class ProxyCore extends events.EventEmitter {
|
|||||||
this.recorder = config.recorder;
|
this.recorder = config.recorder;
|
||||||
|
|
||||||
// init request handler
|
// init request handler
|
||||||
const RequestHandler = util.freshRequire('./requestHandler');
|
const RequestHandler = util.freshRequire('./requestHandler').default;
|
||||||
this.requestHandler = new RequestHandler({
|
this.requestHandler = new RequestHandler({
|
||||||
wsIntercept: config.wsIntercept,
|
wsIntercept: config.wsIntercept,
|
||||||
httpServerPort: config.port, // the http server port for http proxy
|
httpServerPort: config.port, // the http server port for http proxy
|
||||||
|
@ -8,7 +8,7 @@ const Datastore = require('nedb'),
|
|||||||
events = require('events'),
|
events = require('events'),
|
||||||
iconv = require('iconv-lite'),
|
iconv = require('iconv-lite'),
|
||||||
fastJson = require('fast-json-stringify'),
|
fastJson = require('fast-json-stringify'),
|
||||||
proxyUtil = require('./util');
|
proxyUtil = require('./util').default;
|
||||||
|
|
||||||
const wsMessageStingify = fastJson({
|
const wsMessageStingify = fastJson({
|
||||||
title: 'ws message stringify',
|
title: 'ws message stringify',
|
||||||
@ -333,3 +333,4 @@ class Recorder extends events.EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Recorder;
|
module.exports = Recorder;
|
||||||
|
module.exports.default = Recorder;
|
||||||
|
@ -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;
|
|
17
lib/requestHandler/CommonReadableStream.ts
Normal file
17
lib/requestHandler/CommonReadableStream.ts
Normal file
@ -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;
|
@ -1,40 +1,64 @@
|
|||||||
/// <reference path="../../typings/index.d.ts" />
|
/// <reference path="../../typings/index.d.ts" />
|
||||||
|
|
||||||
import RequestErrorHandler from './requestErrorHandler';
|
declare interface ErrorResponse {
|
||||||
|
statusCode: number;
|
||||||
|
header: OneLevelObjectType;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
const
|
import * as url from 'url';
|
||||||
url = require('url'),
|
import * as https from 'https';
|
||||||
https = require('https'),
|
import * as http from 'http';
|
||||||
http = require('http'),
|
import * as color from 'colorful';
|
||||||
color = require('colorful'),
|
import * as buffer from 'buffer';
|
||||||
Buffer = require('buffer').Buffer,
|
import * as Stream from 'stream';
|
||||||
util = require('../util'),
|
import * as zlib from 'zlib';
|
||||||
Stream = require('stream'),
|
import * as brotliTorb from 'brotli';
|
||||||
logUtil = require('../log'),
|
import * as co from 'co';
|
||||||
CommonReadableStream = require('./CommonReadableStream'),
|
import util from '../util';
|
||||||
zlib = require('zlib'),
|
import logUtil from '../log';
|
||||||
brotliTorb = require('brotli'),
|
import RequestErrorHandler from './requestErrorHandler';
|
||||||
co = require('co');
|
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 requestErrorHandler = new RequestErrorHandler();
|
||||||
const DEFAULT_CHUNK_COLLECT_THRESHOLD = 20 * 1024 * 1024; // about 20 mb
|
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
|
// 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
|
* fetch remote response
|
||||||
*
|
*
|
||||||
* @param {string} protocol
|
* @param {string} protocol
|
||||||
* @param {object} options options of http.request
|
* @param {object} options
|
||||||
* @param {buffer} reqData request body
|
* @param {buffer} reqData
|
||||||
* @param {object} config
|
* @param {object} config
|
||||||
* @param {boolean} config.dangerouslyIgnoreUnauthorized
|
* @param {boolean} config.dangerouslyIgnoreUnauthorized
|
||||||
* @param {boolean} config.chunkSizeThreshold
|
* @param {boolean} config.chunkSizeThreshold
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function fetchRemoteResponse(protocol, options, reqData, config) {
|
function fetchRemoteResponse(
|
||||||
reqData = reqData || '';
|
protocol: string, options: https.RequestOptions | http.RequestOptions ,
|
||||||
|
reqData: Buffer, config: {
|
||||||
|
dangerouslyIgnoreUnauthorized: boolean;
|
||||||
|
chunkSizeThreshold: number;
|
||||||
|
}): Promise<any> {
|
||||||
|
reqData = reqData || Buffer.from('');
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
delete options.headers['content-length']; // will reset the content-length after rule
|
delete options.headers['content-length']; // will reset the content-length after rule
|
||||||
delete options.headers['Content-Length'];
|
delete options.headers['Content-Length'];
|
||||||
@ -42,17 +66,18 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
|
|||||||
delete options.headers['transfer-encoding'];
|
delete options.headers['transfer-encoding'];
|
||||||
|
|
||||||
if (config.dangerouslyIgnoreUnauthorized) {
|
if (config.dangerouslyIgnoreUnauthorized) {
|
||||||
options.rejectUnauthorized = false;
|
(options as https.RequestOptions).rejectUnauthorized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.chunkSizeThreshold) {
|
if (!config.chunkSizeThreshold) {
|
||||||
throw new Error('chunkSizeThreshold is required');
|
throw new Error('chunkSizeThreshold is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
//send request
|
const finalHttpModule: typeof https | typeof http = /https/i.test(protocol) ? https : http;
|
||||||
const proxyReq = (/https/i.test(protocol) ? https : http).request(options, (res) => {
|
// send request
|
||||||
|
const proxyReq: http.ClientRequest = (finalHttpModule as any).request(options, (res: http.IncomingMessage): void => {
|
||||||
res.headers = util.getHeaderFromRawHeaders(res.rawHeaders);
|
res.headers = util.getHeaderFromRawHeaders(res.rawHeaders);
|
||||||
//deal response header
|
// deal response header
|
||||||
const statusCode = res.statusCode;
|
const statusCode = res.statusCode;
|
||||||
const resHeader = res.headers;
|
const resHeader = res.headers;
|
||||||
let resDataChunks = []; // array of data chunks or stream
|
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
|
// remove gzip related header, and ungzip the content
|
||||||
// note there are other compression types like deflate
|
// note there are other compression types like deflate
|
||||||
const contentEncoding = resHeader['content-encoding'] || resHeader['Content-Encoding'];
|
const contentEncoding = resHeader['content-encoding'] || resHeader['Content-Encoding'];
|
||||||
const ifServerGzipped = /gzip/i.test(contentEncoding);
|
const ifServerGzipped = /gzip/i.test((contentEncoding as string));
|
||||||
const isServerDeflated = /deflate/i.test(contentEncoding);
|
const isServerDeflated = /deflate/i.test((contentEncoding as string));
|
||||||
const isBrotlied = /br/i.test(contentEncoding);
|
const isBrotlied = /br/i.test((contentEncoding as string));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* when the content is unzipped, update the header content
|
* 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'];
|
||||||
delete resHeader['Content-Encoding'];
|
delete resHeader['Content-Encoding'];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// set origin content length into header
|
// 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
|
// only do unzip when there is res data
|
||||||
if (ifServerGzipped && originContentLen) {
|
if (ifServerGzipped && originContentLen) {
|
||||||
@ -133,7 +158,7 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//deal response data
|
// deal response data
|
||||||
res.on('data', (chunk) => {
|
res.on('data', (chunk) => {
|
||||||
rawResChunks.push(chunk);
|
rawResChunks.push(chunk);
|
||||||
if (resDataStream) { // stream mode
|
if (resDataStream) { // stream mode
|
||||||
@ -176,16 +201,16 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
|
|||||||
/*
|
/*
|
||||||
* get error response for exception scenarios
|
* get error response for exception scenarios
|
||||||
*/
|
*/
|
||||||
function getErrorResponse(error, fullUrl) {
|
function getErrorResponse(error: NodeJS.ErrnoException, fullUrl: string): ErrorResponse {
|
||||||
// default error response
|
// default error response
|
||||||
const errorResponse = {
|
const errorResponse = {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
header: {
|
header: {
|
||||||
'Content-Type': 'text/html; charset=utf-8',
|
'Content-Type': 'text/html; charset=utf-8',
|
||||||
'Proxy-Error': true,
|
'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;
|
return errorResponse;
|
||||||
@ -193,13 +218,16 @@ function getErrorResponse(error, fullUrl) {
|
|||||||
|
|
||||||
|
|
||||||
export default class UserReqHandler {
|
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.userRule = userRule;
|
||||||
this.recorder = recorder;
|
this.recorder = recorder;
|
||||||
this.reqHandlerCtx = ctx;
|
this.reqHandlerCtx = ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
handler(req, userRes) {
|
public handler(req: http.IncomingMessage, userRes: http.ServerResponse): void {
|
||||||
/*
|
/*
|
||||||
note
|
note
|
||||||
req.url is wired
|
req.url is wired
|
||||||
@ -208,7 +236,7 @@ export default class UserReqHandler {
|
|||||||
*/
|
*/
|
||||||
const self = this;
|
const self = this;
|
||||||
const host = req.headers.host;
|
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 fullUrl = protocol === 'http' ? req.url : (protocol + '://' + host + req.url);
|
||||||
|
|
||||||
const urlPattern = url.parse(fullUrl);
|
const urlPattern = url.parse(fullUrl);
|
||||||
@ -246,10 +274,10 @@ export default class UserReqHandler {
|
|||||||
const prepareRequestDetail = () => {
|
const prepareRequestDetail = () => {
|
||||||
const options = {
|
const options = {
|
||||||
hostname: urlPattern.hostname || req.headers.host,
|
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,
|
path,
|
||||||
method: req.method,
|
method: req.method,
|
||||||
headers: req.headers
|
headers: req.headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
requestDetail = {
|
requestDetail = {
|
||||||
@ -257,7 +285,7 @@ export default class UserReqHandler {
|
|||||||
protocol,
|
protocol,
|
||||||
url: fullUrl,
|
url: fullUrl,
|
||||||
requestData: reqData,
|
requestData: reqData,
|
||||||
_req: req
|
_req: req,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@ -294,7 +322,7 @@ export default class UserReqHandler {
|
|||||||
if (!responseInfo) {
|
if (!responseInfo) {
|
||||||
throw new Error('failed to get response info');
|
throw new Error('failed to get response info');
|
||||||
} else if (!responseInfo.statusCode) {
|
} 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) {
|
} else if (!responseInfo.header) {
|
||||||
throw new Error('filed to get response header');
|
throw new Error('filed to get response header');
|
||||||
}
|
}
|
||||||
@ -326,7 +354,7 @@ export default class UserReqHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return responseInfo;
|
return responseInfo;
|
||||||
}
|
};
|
||||||
|
|
||||||
// fetch complete request data
|
// fetch complete request data
|
||||||
co(fetchReqData)
|
co(fetchReqData)
|
||||||
@ -342,29 +370,29 @@ export default class UserReqHandler {
|
|||||||
protocol,
|
protocol,
|
||||||
url: protocol + '://' + host + path,
|
url: protocol + '://' + host + path,
|
||||||
req,
|
req,
|
||||||
startTime: new Date().getTime()
|
startTime: new Date().getTime(),
|
||||||
};
|
};
|
||||||
resourceInfoId = self.recorder.appendRecord(resourceInfo);
|
resourceInfoId = self.recorder.appendRecord(resourceInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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);
|
self.recorder && self.recorder.updateRecord(resourceInfoId, resourceInfo);
|
||||||
} catch (e) { }
|
} catch (e) { console.error(e); }
|
||||||
})
|
})
|
||||||
|
|
||||||
// invoke rule before sending request
|
// invoke rule before sending request
|
||||||
.then(co.wrap(function *() {
|
.then(co.wrap(function*(): Generator {
|
||||||
const userModifiedInfo = (yield self.userRule.beforeSendRequest(Object.assign({}, requestDetail))) || {};
|
const userModifiedInfo = (yield self.userRule.beforeSendRequest(Object.assign({}, requestDetail))) || {};
|
||||||
const finalReqDetail = {};
|
const finalReqDetail = {};
|
||||||
['protocol', 'requestOptions', 'requestData', 'response'].map((key) => {
|
['protocol', 'requestOptions', 'requestData', 'response'].map((key) => {
|
||||||
finalReqDetail[key] = userModifiedInfo[key] || requestDetail[key]
|
finalReqDetail[key] = userModifiedInfo[key] || requestDetail[key];
|
||||||
});
|
});
|
||||||
return finalReqDetail;
|
return finalReqDetail;
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// route user config
|
// route user config
|
||||||
.then(co.wrap(function *(userConfig) {
|
.then(co.wrap(function *(userConfig: AnyProxyRequestDetail): Generator {
|
||||||
if (userConfig.response) {
|
if (userConfig.response) {
|
||||||
// user-assigned local response
|
// user-assigned local response
|
||||||
userConfig._directlyPassToRespond = true;
|
userConfig._directlyPassToRespond = true;
|
||||||
@ -379,7 +407,7 @@ export default class UserReqHandler {
|
|||||||
statusCode: remoteResponse.statusCode,
|
statusCode: remoteResponse.statusCode,
|
||||||
header: remoteResponse.header,
|
header: remoteResponse.header,
|
||||||
body: remoteResponse.body,
|
body: remoteResponse.body,
|
||||||
rawBody: remoteResponse.rawBody
|
rawBody: remoteResponse.rawBody,
|
||||||
},
|
},
|
||||||
_res: remoteResponse._res,
|
_res: remoteResponse._res,
|
||||||
};
|
};
|
||||||
@ -389,18 +417,19 @@ export default class UserReqHandler {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// invoke rule before responding to client
|
// invoke rule before responding to client
|
||||||
.then(co.wrap(function *(responseData) {
|
.then(co.wrap(function*(responseData: AnyProxyReponseDetail): Generator {
|
||||||
if (responseData._directlyPassToRespond) {
|
if (responseData._directlyPassToRespond) {
|
||||||
return responseData;
|
return responseData;
|
||||||
} else if (responseData.response.body && responseData.response.body instanceof CommonReadableStream) { // in stream mode
|
} else if (responseData.response.body && responseData.response.body instanceof CommonReadableStream) { // in stream mode
|
||||||
return responseData;
|
return responseData;
|
||||||
} else {
|
} else {
|
||||||
// TODO: err etimeout
|
// 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);
|
logUtil.printLog(util.collectErrorLog(error), logUtil.T_ERR);
|
||||||
|
|
||||||
let errorResponse = getErrorResponse(error, fullUrl);
|
let errorResponse = getErrorResponse(error, fullUrl);
|
||||||
@ -411,18 +440,18 @@ export default class UserReqHandler {
|
|||||||
if (userResponse && userResponse.response && userResponse.response.header) {
|
if (userResponse && userResponse.response && userResponse.response.header) {
|
||||||
errorResponse = userResponse.response;
|
errorResponse = userResponse.response;
|
||||||
}
|
}
|
||||||
} catch (e) { }
|
} catch (e) { console.error(e); }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response: errorResponse
|
response: errorResponse,
|
||||||
};
|
};
|
||||||
}))
|
}))
|
||||||
.then(sendFinalResponse)
|
.then(sendFinalResponse)
|
||||||
|
|
||||||
//update record info
|
// update record info
|
||||||
.then((responseInfo) => {
|
.then((responseInfo) => {
|
||||||
resourceInfo.endTime = new Date().getTime();
|
resourceInfo.endTime = new Date().getTime();
|
||||||
resourceInfo.res = { //construct a self-defined res object
|
resourceInfo.res = { // construct a self-defined res object
|
||||||
statusCode: responseInfo.statusCode,
|
statusCode: responseInfo.statusCode,
|
||||||
headers: responseInfo.header,
|
headers: responseInfo.header,
|
||||||
};
|
};
|
@ -1,16 +1,31 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
/// <reference path="../../typings/index.d.ts" />
|
||||||
|
|
||||||
import UserReqHandler from './UserReqHandler';
|
import UserReqHandler from './UserReqHandler';
|
||||||
|
import HttpsServerMgr from '../httpsServerMgr';
|
||||||
|
import Recorder from '../recorder';
|
||||||
|
|
||||||
const
|
import * as color from 'colorful';
|
||||||
net = require('net'),
|
import * as co from 'co';
|
||||||
color = require('colorful'),
|
import * as net from 'net';
|
||||||
util = require('../util'),
|
import * as http from 'http';
|
||||||
logUtil = require('../log'),
|
import * as WebSocket from 'ws';
|
||||||
co = require('co'),
|
import util from '../util';
|
||||||
WebSocket = require('ws'),
|
import logUtil from '../log';
|
||||||
CommonReadableStream = require('./CommonReadableStream'),
|
import CommonReadableStream from './CommonReadableStream';
|
||||||
HttpsServerMgr = require('../httpsServerMgr');
|
|
||||||
|
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:
|
* get request info from the ws client, includes:
|
||||||
@ -22,7 +37,7 @@ const
|
|||||||
@param @required wsClient the ws client of WebSocket
|
@param @required wsClient the ws client of WebSocket
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function getWsReqInfo(wsReq) {
|
function getWsReqInfo(wsReq: http.IncomingMessage): IWsReqInfo {
|
||||||
const headers = wsReq.headers || {};
|
const headers = wsReq.headers || {};
|
||||||
const host = headers.host;
|
const host = headers.host;
|
||||||
const hostName = host.split(':')[0];
|
const hostName = host.split(':')[0];
|
||||||
@ -31,12 +46,12 @@ function getWsReqInfo(wsReq) {
|
|||||||
// TODO 如果是windows机器,url是不是全路径?需要对其过滤,取出
|
// TODO 如果是windows机器,url是不是全路径?需要对其过滤,取出
|
||||||
const path = wsReq.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,
|
* construct the request headers based on original connection,
|
||||||
* but delete the `sec-websocket-*` headers as they are already consumed by AnyProxy
|
* 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 originHeaders = Object.assign({}, headers);
|
||||||
const originHeaderKeys = Object.keys(originHeaders);
|
const originHeaderKeys = Object.keys(originHeaders);
|
||||||
originHeaderKeys.forEach((key) => {
|
originHeaderKeys.forEach((key) => {
|
||||||
@ -49,16 +64,15 @@ function getWsReqInfo(wsReq) {
|
|||||||
delete originHeaders.connection;
|
delete originHeaders.connection;
|
||||||
delete originHeaders.upgrade;
|
delete originHeaders.upgrade;
|
||||||
return originHeaders;
|
return originHeaders;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
headers: headers, // the full headers of origin ws connection
|
headers, // the full headers of origin ws connection
|
||||||
noWsHeaders: getNoWsHeaders(),
|
noWsHeaders: getNoWsHeaders(),
|
||||||
hostName: hostName,
|
hostName,
|
||||||
port: port,
|
port,
|
||||||
path: path,
|
path,
|
||||||
protocol: isEncript ? 'wss' : 'ws'
|
protocol: isEncript ? 'wss' : 'ws',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -70,12 +84,13 @@ function getWsReqInfo(wsReq) {
|
|||||||
* @param {object} httpsServerMgr
|
* @param {object} httpsServerMgr
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
function getConnectReqHandler(userRule: AnyProxyRule, recorder: Recorder, httpsServerMgr: HttpsServerMgr)
|
||||||
const reqHandlerCtx = this; reqHandlerCtx.conns = new Map(); reqHandlerCtx.cltSockets = new Map()
|
: (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) {
|
return function(req: http.IncomingMessage, cltSocket: net.Socket, head: Buffer[]): void {
|
||||||
const host = req.url.split(':')[0],
|
const host = req.url.split(':')[0];
|
||||||
targetPort = req.url.split(':')[1];
|
const targetPort = req.url.split(':')[1];
|
||||||
let shouldIntercept;
|
let shouldIntercept;
|
||||||
let interceptWsRequest = false;
|
let interceptWsRequest = false;
|
||||||
let requestDetail;
|
let requestDetail;
|
||||||
@ -90,12 +105,12 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
4.1 if (websocket || do_not_intercept) --> pipe to target server
|
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
|
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
|
// determine whether to use the man-in-the-middle server
|
||||||
logUtil.printLog(color.green('received https CONNECT request ' + host));
|
logUtil.printLog(color.green('received https CONNECT request ' + host));
|
||||||
requestDetail = {
|
requestDetail = {
|
||||||
host: req.url,
|
host: req.url,
|
||||||
_req: req
|
_req: req,
|
||||||
};
|
};
|
||||||
// the return value in default rule is null
|
// the return value in default rule is null
|
||||||
// so if the value is null, will take it as final value
|
// so if the value is null, will take it as final value
|
||||||
@ -106,14 +121,14 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
shouldIntercept = reqHandlerCtx.forceProxyHttps;
|
shouldIntercept = reqHandlerCtx.forceProxyHttps;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() =>
|
.then(() => {
|
||||||
new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// mark socket connection as established, to detect the request protocol
|
// mark socket connection as established, to detect the request protocol
|
||||||
cltSocket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', resolve);
|
cltSocket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', resolve);
|
||||||
})
|
});
|
||||||
)
|
})
|
||||||
.then(() =>
|
.then(() => {
|
||||||
new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
cltSocket.on('data', (chunk) => {
|
cltSocket.on('data', (chunk) => {
|
||||||
requestStream.push(chunk);
|
requestStream.push(chunk);
|
||||||
@ -139,8 +154,8 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
cltSocket.on('end', () => {
|
cltSocket.on('end', () => {
|
||||||
requestStream.push(null);
|
requestStream.push(null);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
)
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
// log and recorder
|
// log and recorder
|
||||||
if (shouldIntercept) {
|
if (shouldIntercept) {
|
||||||
@ -149,7 +164,7 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
logUtil.printLog('will bypass the man-in-the-middle proxy');
|
logUtil.printLog('will bypass the man-in-the-middle proxy');
|
||||||
}
|
}
|
||||||
|
|
||||||
//record
|
// record
|
||||||
if (recorder) {
|
if (recorder) {
|
||||||
resourceInfo = {
|
resourceInfo = {
|
||||||
host,
|
host,
|
||||||
@ -157,7 +172,7 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
path: '',
|
path: '',
|
||||||
url: 'https://' + host,
|
url: 'https://' + host,
|
||||||
req,
|
req,
|
||||||
startTime: new Date().getTime()
|
startTime: new Date().getTime(),
|
||||||
};
|
};
|
||||||
resourceInfoId = recorder.appendRecord(resourceInfo);
|
resourceInfoId = recorder.appendRecord(resourceInfo);
|
||||||
}
|
}
|
||||||
@ -168,18 +183,19 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
// server info from the original request
|
// server info from the original request
|
||||||
const originServer = {
|
const originServer = {
|
||||||
host,
|
host,
|
||||||
port: (targetPort === 80) ? 443 : targetPort
|
port: (targetPort === '80') ? 443 : targetPort,
|
||||||
}
|
};
|
||||||
|
|
||||||
const localHttpServer = {
|
const localHttpServer = {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: reqHandlerCtx.httpServerPort
|
port: reqHandlerCtx.httpServerPort,
|
||||||
}
|
};
|
||||||
|
|
||||||
// for ws request, redirect them to local ws server
|
// for ws request, redirect them to local ws server
|
||||||
return interceptWsRequest ? localHttpServer : originServer;
|
return interceptWsRequest ? localHttpServer : originServer;
|
||||||
} else {
|
} 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) => {
|
.then((serverInfo) => {
|
||||||
@ -189,7 +205,7 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const conn = net.connect(serverInfo.port, serverInfo.host, () => {
|
const conn = net.connect(serverInfo.port, serverInfo.host, () => {
|
||||||
//throttle for direct-foward https
|
// throttle for direct-foward https
|
||||||
if (global._throttle && !shouldIntercept) {
|
if (global._throttle && !shouldIntercept) {
|
||||||
requestStream.pipe(conn);
|
requestStream.pipe(conn);
|
||||||
conn.pipe(global._throttle.throttle()).pipe(cltSocket);
|
conn.pipe(global._throttle.throttle()).pipe(cltSocket);
|
||||||
@ -205,8 +221,8 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
reject(e);
|
reject(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
reqHandlerCtx.conns.set(serverInfo.host + ':' + serverInfo.port, conn)
|
reqHandlerCtx.conns.set(serverInfo.host + ':' + serverInfo.port, conn);
|
||||||
reqHandlerCtx.cltSockets.set(serverInfo.host + ':' + serverInfo.port, cltSocket)
|
reqHandlerCtx.cltSockets.set(serverInfo.host + ':' + serverInfo.port, cltSocket);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -220,40 +236,40 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
|||||||
recorder && recorder.updateRecord(resourceInfoId, resourceInfo);
|
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);
|
logUtil.printLog(util.collectErrorLog(error), logUtil.T_ERR);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
yield userRule.onConnectError(requestDetail, error);
|
yield userRule.onConnectError(requestDetail, error);
|
||||||
} catch (e) { }
|
} catch (e) { console.error(e); }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let errorHeader = 'Proxy-Error: true\r\n';
|
let errorHeader = 'Proxy-Error: true\r\n';
|
||||||
errorHeader += 'Proxy-Error-Message: ' + (error || 'null') + '\r\n';
|
errorHeader += 'Proxy-Error-Message: ' + (error || 'null') + '\r\n';
|
||||||
errorHeader += 'Content-Type: text/html\r\n';
|
errorHeader += 'Content-Type: text/html\r\n';
|
||||||
cltSocket.write('HTTP/1.1 502\r\n' + errorHeader + '\r\n\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
|
* 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;
|
const self = this;
|
||||||
try {
|
try {
|
||||||
let resourceInfoId = -1;
|
let resourceInfoId = -1;
|
||||||
const resourceInfo = {
|
const resourceInfo: AnyProxyRecorder.ResourceInfo = {
|
||||||
wsMessages: [] // all ws messages go through AnyProxy
|
wsMessages: [] as AnyProxyRecorder.WsResourceInfo[], // all ws messages go through AnyProxy
|
||||||
};
|
};
|
||||||
const clientMsgQueue = [];
|
const clientMsgQueue = [];
|
||||||
const serverInfo = getWsReqInfo(wsReq);
|
const serverInfo = getWsReqInfo(wsReq);
|
||||||
const wsUrl = `${serverInfo.protocol}://${serverInfo.hostName}:${serverInfo.port}${serverInfo.path}`;
|
const wsUrl = `${serverInfo.protocol}://${serverInfo.hostName}:${serverInfo.port}${serverInfo.path}`;
|
||||||
const proxyWs = new WebSocket(wsUrl, '', {
|
const proxyWs = new WebSocket(wsUrl, '', {
|
||||||
rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized,
|
rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized,
|
||||||
headers: serverInfo.noWsHeaders
|
headers: serverInfo.noWsHeaders as IWebsocketOptionHeaders,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (recorder) {
|
if (recorder) {
|
||||||
@ -263,7 +279,7 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
|
|||||||
path: serverInfo.path,
|
path: serverInfo.path,
|
||||||
url: wsUrl,
|
url: wsUrl,
|
||||||
req: wsReq,
|
req: wsReq,
|
||||||
startTime: new Date().getTime()
|
startTime: new Date().getTime(),
|
||||||
});
|
});
|
||||||
resourceInfoId = recorder.appendRecord(resourceInfo);
|
resourceInfoId = recorder.appendRecord(resourceInfo);
|
||||||
}
|
}
|
||||||
@ -283,7 +299,7 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
|
|||||||
} else {
|
} else {
|
||||||
clientMsgQueue.push(message);
|
clientMsgQueue.push(message);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* consume the message in queue when the proxy ws is not ready yet
|
* 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();
|
const message = clientMsgQueue.shift();
|
||||||
proxyWs.send(message);
|
proxyWs.send(message);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the source ws is closed, we need to close the target websocket.
|
* 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 getCloseFromOriginEvent = (event) => {
|
||||||
const code = event.code || '';
|
const code = event.code || '';
|
||||||
const reason = event.reason || '';
|
const reason = event.reason || '';
|
||||||
let targetCode = '';
|
let targetCode;
|
||||||
let targetReason = '';
|
let targetReason = '';
|
||||||
if (code >= 1004 && code <= 1006) {
|
if (code >= 1004 && code <= 1006) {
|
||||||
targetCode = 1000; // normal closure
|
targetCode = 1000; // normal closure
|
||||||
@ -315,9 +331,9 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
code: targetCode,
|
code: targetCode,
|
||||||
reason: targetReason
|
reason: targetReason,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* consruct a message Record from message event
|
* consruct a message Record from message event
|
||||||
@ -329,7 +345,7 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
|
|||||||
const message = {
|
const message = {
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
message: messageEvent.data,
|
message: messageEvent.data,
|
||||||
isToServer: isToServer
|
isToServer,
|
||||||
};
|
};
|
||||||
|
|
||||||
// resourceInfo.wsMessages.push(message);
|
// resourceInfo.wsMessages.push(message);
|
||||||
@ -338,15 +354,15 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
|
|||||||
|
|
||||||
proxyWs.onopen = () => {
|
proxyWs.onopen = () => {
|
||||||
consumeMsgQueue();
|
consumeMsgQueue();
|
||||||
}
|
};
|
||||||
|
|
||||||
// this event is fired when the connection is build and headers is returned
|
// this event is fired when the connection is build and headers is returned
|
||||||
proxyWs.on('upgrade', (response) => {
|
proxyWs.on('upgrade', (response) => {
|
||||||
resourceInfo.endTime = new Date().getTime();
|
resourceInfo.endTime = new Date().getTime();
|
||||||
const headers = response.headers;
|
const headers = response.headers;
|
||||||
resourceInfo.res = { //construct a self-defined res object
|
resourceInfo.res = { // construct a self-defined res object
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
headers: headers,
|
headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
resourceInfo.statusCode = response.statusCode;
|
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
|
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
|
||||||
wsClient.close(1001, e.message);
|
wsClient.close(1001, e.message);
|
||||||
proxyWs.close(1001);
|
proxyWs.close(1001);
|
||||||
}
|
};
|
||||||
|
|
||||||
proxyWs.onmessage = (event) => {
|
proxyWs.onmessage = (event) => {
|
||||||
recordMessage(event, false);
|
recordMessage(event, false);
|
||||||
wsClient.readyState === 1 && wsClient.send(event.data);
|
wsClient.readyState === 1 && wsClient.send(event.data);
|
||||||
}
|
};
|
||||||
|
|
||||||
proxyWs.onclose = (event) => {
|
proxyWs.onclose = (event) => {
|
||||||
logUtil.debug(`proxy ws closed with code: ${event.code} and reason: ${event.reason}`);
|
logUtil.debug(`proxy ws closed with code: ${event.code} and reason: ${event.reason}`);
|
||||||
const targetCloseInfo = getCloseFromOriginEvent(event);
|
const targetCloseInfo = getCloseFromOriginEvent(event);
|
||||||
wsClient.readyState !== 3 && wsClient.close(targetCloseInfo.code, targetCloseInfo.reason);
|
wsClient.readyState !== 3 && wsClient.close(targetCloseInfo.code, targetCloseInfo.reason);
|
||||||
}
|
};
|
||||||
|
|
||||||
wsClient.onmessage = (event) => {
|
wsClient.onmessage = (event) => {
|
||||||
recordMessage(event, true);
|
recordMessage(event, true);
|
||||||
sendProxyMessage(event);
|
sendProxyMessage(event);
|
||||||
}
|
};
|
||||||
|
|
||||||
wsClient.onclose = (event) => {
|
wsClient.onclose = (event) => {
|
||||||
logUtil.debug(`original ws closed with code: ${event.code} and reason: ${event.reason}`);
|
logUtil.debug(`original ws closed with code: ${event.code} and reason: ${event.reason}`);
|
||||||
const targetCloseInfo = getCloseFromOriginEvent(event);
|
const targetCloseInfo = getCloseFromOriginEvent(event);
|
||||||
proxyWs.readyState !== 3 && proxyWs.close(targetCloseInfo.code, targetCloseInfo.reason);
|
proxyWs.readyState !== 3 && proxyWs.close(targetCloseInfo.code, targetCloseInfo.reason);
|
||||||
}
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logUtil.debug('WebSocket Proxy Error:' + e.message);
|
logUtil.debug('WebSocket Proxy Error:' + e.message);
|
||||||
logUtil.debug(e.stack);
|
logUtil.debug(e.stack);
|
||||||
@ -392,7 +408,14 @@ function getWsHandler(userRule, recorder, wsClient, wsReq) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RequestHandler {
|
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.
|
* Creates an instance of RequestHandler.
|
||||||
*
|
*
|
||||||
@ -405,7 +428,7 @@ class RequestHandler {
|
|||||||
*
|
*
|
||||||
* @memberOf RequestHandler
|
* @memberOf RequestHandler
|
||||||
*/
|
*/
|
||||||
constructor(config, rule, recorder) {
|
constructor(config: AnyProxyConfig, rule: AnyProxyRule, recorder: Recorder) {
|
||||||
const reqHandlerCtx = this;
|
const reqHandlerCtx = this;
|
||||||
this.forceProxyHttps = false;
|
this.forceProxyHttps = false;
|
||||||
this.dangerouslyIgnoreUnauthorized = false;
|
this.dangerouslyIgnoreUnauthorized = false;
|
||||||
@ -425,8 +448,8 @@ class RequestHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.httpServerPort = config.httpServerPort;
|
this.httpServerPort = config.httpServerPort;
|
||||||
const default_rule = util.freshRequire('./rule_default');
|
const defaultRule = util.freshRequire('./rule_default');
|
||||||
const userRule = util.merge(default_rule, rule);
|
const userRule = util.merge(defaultRule, rule);
|
||||||
|
|
||||||
const userReqHandler = new UserReqHandler(reqHandlerCtx, userRule, recorder);
|
const userReqHandler = new UserReqHandler(reqHandlerCtx, userRule, recorder);
|
||||||
reqHandlerCtx.userRequestHandler = userReqHandler.handler.bind(userReqHandler);
|
reqHandlerCtx.userRequestHandler = userReqHandler.handler.bind(userReqHandler);
|
||||||
@ -434,11 +457,11 @@ class RequestHandler {
|
|||||||
|
|
||||||
reqHandlerCtx.httpsServerMgr = new HttpsServerMgr({
|
reqHandlerCtx.httpsServerMgr = new HttpsServerMgr({
|
||||||
handler: reqHandlerCtx.userRequestHandler,
|
handler: reqHandlerCtx.userRequestHandler,
|
||||||
wsHandler: reqHandlerCtx.wsHandler // websocket
|
wsHandler: reqHandlerCtx.wsHandler, // websocket
|
||||||
});
|
});
|
||||||
|
|
||||||
this.connectReqHandler = getConnectReqHandler.apply(reqHandlerCtx, [userRule, recorder, reqHandlerCtx.httpsServerMgr]);
|
this.connectReqHandler = getConnectReqHandler.apply(reqHandlerCtx, [userRule, recorder, reqHandlerCtx.httpsServerMgr]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RequestHandler;
|
export default RequestHandler;
|
@ -4,8 +4,8 @@
|
|||||||
* handle all request error here,
|
* handle all request error here,
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const pug = require('pug');
|
import * as pug from 'pug';
|
||||||
const path = require('path');
|
import * as path from 'path';
|
||||||
|
|
||||||
const error502PugFn = pug.compileFile(path.join(__dirname, '../resource/502.pug'));
|
const error502PugFn = pug.compileFile(path.join(__dirname, '../resource/502.pug'));
|
||||||
const certPugFn = pug.compileFile(path.join(__dirname, '../resource/cert_error.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
|
* get error content for certification issues
|
||||||
*/
|
*/
|
||||||
function getCertErrorContent(error, fullUrl) {
|
function getCertErrorContent(error: NodeJS.ErrnoException, fullUrl: string): string {
|
||||||
let content;
|
let content;
|
||||||
const title = 'The connection is not private. ';
|
const title = 'The connection is not private. ';
|
||||||
let explain = 'There are error with the certfication of the site.';
|
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': {
|
case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
|
||||||
explain = 'The certfication of the site you are visiting is not issued by a known agency, '
|
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.</br>'
|
+ 'It usually happenes when the cert is a self-signed one.</br>'
|
||||||
+ 'If you know and trust the site, you can run AnyProxy with option <strong>-ignore-unauthorized-ssl</strong> to continue.'
|
+ 'If you know and trust the site, you can run AnyProxy with option'
|
||||||
|
+ ' <strong>-ignore-unauthorized-ssl</strong> to continue.'
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
explain = ''
|
explain = '';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
content = certPugFn({
|
content = certPugFn({
|
||||||
title: title,
|
title,
|
||||||
explain: explain,
|
explain,
|
||||||
code: error.code
|
code: error.code,
|
||||||
});
|
});
|
||||||
} catch (parseErro) {
|
} catch (parseErro) {
|
||||||
content = error.stack;
|
content = error.stack;
|
||||||
@ -47,14 +48,14 @@ function getCertErrorContent(error, fullUrl) {
|
|||||||
/*
|
/*
|
||||||
* get the default error content
|
* get the default error content
|
||||||
*/
|
*/
|
||||||
function getDefaultErrorCotent(error, fullUrl) {
|
function getDefaultErrorCotent(error: NodeJS.ErrnoException, fullUrl: string): string {
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
content = error502PugFn({
|
content = error502PugFn({
|
||||||
error,
|
error,
|
||||||
url: fullUrl,
|
url: fullUrl,
|
||||||
errorStack: error.stack.split(/\n/)
|
errorStack: error.stack.split(/\n/),
|
||||||
});
|
});
|
||||||
} catch (parseErro) {
|
} catch (parseErro) {
|
||||||
content = error.stack;
|
content = error.stack;
|
||||||
@ -66,19 +67,22 @@ function getDefaultErrorCotent(error, fullUrl) {
|
|||||||
/*
|
/*
|
||||||
* get mapped error content for each error
|
* get mapped error content for each error
|
||||||
*/
|
*/
|
||||||
module.exports.getErrorContent = function (error, fullUrl) {
|
export default class RequestErrorHandler {
|
||||||
let content = '';
|
public getErrorContent: (error: NodeJS.ErrnoException, fullUrl: string) => string =
|
||||||
error = error || {};
|
function(error: NodeJS.ErrnoException, fullUrl: string): string {
|
||||||
switch (error.code) {
|
let content = '';
|
||||||
case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
|
error = error || { name: 'default_by_anyproxy', message: 'this is the default error'};
|
||||||
content = getCertErrorContent(error, fullUrl);
|
switch (error.code) {
|
||||||
break;
|
case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
|
||||||
}
|
content = getCertErrorContent(error, fullUrl);
|
||||||
default: {
|
break;
|
||||||
content = getDefaultErrorCotent(error, fullUrl);
|
}
|
||||||
break;
|
default: {
|
||||||
}
|
content = getDefaultErrorCotent(error, fullUrl);
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
|
};
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const proxyUtil = require('./util');
|
const proxyUtil = require('./util').default;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const request = require('request');
|
const request = require('request');
|
||||||
|
570
lib/util.ts
570
lib/util.ts
@ -17,330 +17,330 @@ import { execSync } from 'child_process';
|
|||||||
import logUtil from './log';
|
import logUtil from './log';
|
||||||
|
|
||||||
|
|
||||||
const networkInterfaces = require('os').networkInterfaces();
|
const networkInterfaces = require('os').networkInterfaces();
|
||||||
|
|
||||||
// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
|
// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
|
||||||
function lower_keys (obj: object): object {
|
function lower_keys (obj: object): object {
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
const val = obj[key];
|
const val = obj[key];
|
||||||
delete obj[key];
|
delete obj[key];
|
||||||
|
|
||||||
obj[key.toLowerCase()] = val;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAnyProxyHome(): string {
|
return obj;
|
||||||
const home = path.join(getUserHome(), '/.anyproxy/');
|
};
|
||||||
if (!fs.existsSync(home)) {
|
|
||||||
fs.mkdirSync(home);
|
function merge (baseObj: object, extendObj: object): object {
|
||||||
}
|
for (const key in extendObj) {
|
||||||
return home;
|
baseObj[key] = extendObj[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAnyProxyPath (pathName): string {
|
return baseObj;
|
||||||
const home = getAnyProxyHome();
|
};
|
||||||
const targetPath = path.join(home, pathName);
|
|
||||||
if (!fs.existsSync(targetPath)) {
|
function getUserHome(): string {
|
||||||
fs.mkdirSync(targetPath);
|
return process.env.HOME || process.env.USERPROFILE;
|
||||||
}
|
}
|
||||||
return targetPath;
|
|
||||||
|
function getAnyProxyHome(): string {
|
||||||
|
const home = path.join(getUserHome(), '/.anyproxy/');
|
||||||
|
if (!fs.existsSync(home)) {
|
||||||
|
fs.mkdirSync(home);
|
||||||
}
|
}
|
||||||
|
return home;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
function getAnyProxyPath (pathName): string {
|
||||||
* 简易字符串render替换
|
const home = getAnyProxyHome();
|
||||||
*/
|
const targetPath = path.join(home, pathName);
|
||||||
function simpleRender (str: string, object: object, regexp: RegExp) {
|
if (!fs.existsSync(targetPath)) {
|
||||||
return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
|
fs.mkdirSync(targetPath);
|
||||||
if (match.charAt(0) === '\\') {
|
}
|
||||||
return match.slice(1);
|
return targetPath;
|
||||||
}
|
}
|
||||||
return (object[name] != null) ? object[name] : '';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取指定目录下的子目录
|
* 简易字符串render替换
|
||||||
*/
|
*/
|
||||||
function filewalker (root: string, cb: Function) {
|
function simpleRender (str: string, object: object, regexp: RegExp) {
|
||||||
root = root || process.cwd();
|
return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
|
||||||
|
if (match.charAt(0) === '\\') {
|
||||||
const ret = {
|
return match.slice(1);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return (object[name] != null) ? object[name] : '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取指定目录下的子目录
|
||||||
|
*/
|
||||||
|
function filewalker (root: string, cb: Function) {
|
||||||
|
root = root || process.cwd();
|
||||||
|
|
||||||
|
const ret = {
|
||||||
|
directory: [],
|
||||||
|
file: []
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
fs.readdir(root, (err, list) => {
|
||||||
* remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS
|
if (list && list.length) {
|
||||||
*/
|
list.map((item) => {
|
||||||
function freshRequire (modulePath: string): NodeModule {
|
const fullPath = path.join(root, item),
|
||||||
delete require.cache[require.resolve(modulePath)];
|
stat = fs.lstatSync(fullPath);
|
||||||
return require(modulePath);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
if (stat.isFile()) {
|
||||||
* format the date string
|
ret.file.push({
|
||||||
* @param date Date or timestamp
|
name: item,
|
||||||
* @param formatter YYYYMMDDHHmmss
|
fullPath
|
||||||
*/
|
});
|
||||||
function formatDate (date: Date | number, formatter: string): string {
|
} else if (stat.isDirectory()) {
|
||||||
let finalDate : Date;
|
ret.directory.push({
|
||||||
if (typeof date !== 'object') {
|
name: item,
|
||||||
finalDate = new Date(date);
|
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<string>) {
|
||||||
|
const headerObj = {};
|
||||||
|
const _handleSetCookieHeader = function (key, value) {
|
||||||
|
if (headerObj[key].constructor === Array) {
|
||||||
|
headerObj[key].push(value);
|
||||||
} else {
|
} 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];
|
||||||
|
|
||||||
/**
|
if (typeof value === 'string') {
|
||||||
* get headers(Object) from rawHeaders(Array)
|
value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串
|
||||||
* @param rawHeaders [key, value, key2, value2, ...]
|
}
|
||||||
|
|
||||||
*/
|
if (!headerObj[key]) {
|
||||||
|
headerObj[key] = value;
|
||||||
function getHeaderFromRawHeaders (rawHeaders: Array<string>) {
|
|
||||||
const headerObj = {};
|
|
||||||
const _handleSetCookieHeader = function (key, value) {
|
|
||||||
if (headerObj[key].constructor === Array) {
|
|
||||||
headerObj[key].push(value);
|
|
||||||
} else {
|
} else {
|
||||||
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);
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
// headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
headerObj[key] = headerObj[key] + ',' + value;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return headerObj;
|
}
|
||||||
};
|
return headerObj;
|
||||||
|
};
|
||||||
|
|
||||||
function getAllIpAddress (): Array<string> {
|
function getAllIpAddress (): Array<string> {
|
||||||
const allIp = [];
|
const allIp = [];
|
||||||
|
|
||||||
Object.keys(networkInterfaces).map((nic) => {
|
Object.keys(networkInterfaces).map((nic) => {
|
||||||
networkInterfaces[nic].filter((detail) => {
|
networkInterfaces[nic].filter((detail) => {
|
||||||
if (detail.family.toLowerCase() === 'ipv4') {
|
if (detail.family.toLowerCase() === 'ipv4') {
|
||||||
allIp.push(detail.address);
|
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'];
|
if (ifClearFolderItself) {
|
||||||
};
|
try {
|
||||||
|
// ref: https://github.com/shelljs/shelljs/issues/49
|
||||||
function deleteFolderContentsRecursive(dirPath: string, ifClearFolderItself: boolean): void {
|
const start = Date.now();
|
||||||
if (!dirPath.trim() || dirPath === '/') {
|
while (true) {
|
||||||
throw new Error('can_not_delete_this_dir');
|
try {
|
||||||
}
|
fs.rmdirSync(dirPath);
|
||||||
|
break;
|
||||||
if (fs.existsSync(dirPath)) {
|
} catch (er) {
|
||||||
fs.readdirSync(dirPath).forEach((file) => {
|
if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) {
|
||||||
const curPath = path.join(dirPath, file);
|
// Retry on windows, sometimes it takes a little time before all the files in the directory are gone
|
||||||
if (fs.lstatSync(curPath).isDirectory()) {
|
if (Date.now() - start > 1000) throw er;
|
||||||
deleteFolderContentsRecursive(curPath, true);
|
} else if (er.code === 'ENOENT') {
|
||||||
} 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);
|
|
||||||
break;
|
break;
|
||||||
} catch (er) {
|
} else {
|
||||||
if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) {
|
throw er;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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<number> {
|
function getFreePort (): Promise<number> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const server = require('net').createServer();
|
const server = require('net').createServer();
|
||||||
server.unref();
|
server.unref();
|
||||||
server.on('error', reject);
|
server.on('error', reject);
|
||||||
server.listen(0, () => {
|
server.listen(0, () => {
|
||||||
const port = server.address().port;
|
const port = server.address().port;
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
resolve(port);
|
resolve(port);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function collectErrorLog (error: any): string {
|
function collectErrorLog (error: any): string {
|
||||||
if (error && error.code && error.toString()) {
|
if (error && error.code && error.toString()) {
|
||||||
return error.toString();
|
return error.toString();
|
||||||
} else {
|
} else {
|
||||||
let result = [error, error.stack].join('\n');
|
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;
|
|
||||||
try {
|
try {
|
||||||
stdout = execSync(cmd);
|
const errorString = error.toString();
|
||||||
} catch (err) {
|
if (errorString.indexOf('You may only yield a function') >= 0) {
|
||||||
stdout = err.stdout;
|
result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq';
|
||||||
status = err.status;
|
}
|
||||||
}
|
} catch (e) {}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
function isFunc (source: object): boolean {
|
||||||
stdout: stdout.toString(),
|
return source && Object.prototype.toString.call(source) === '[object Function]';
|
||||||
status
|
};
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = {
|
const Util = {
|
||||||
@ -364,9 +364,9 @@ const Util = {
|
|||||||
deleteFolderContentsRecursive,
|
deleteFolderContentsRecursive,
|
||||||
execScriptSync,
|
execScriptSync,
|
||||||
guideToHomePage,
|
guideToHomePage,
|
||||||
formatDate
|
formatDate,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Util;
|
export default Util;
|
||||||
module.exports = Util;
|
|
||||||
// })();
|
|
||||||
|
@ -9,7 +9,7 @@ const express = require('express'),
|
|||||||
path = require('path'),
|
path = require('path'),
|
||||||
events = require('events'),
|
events = require('events'),
|
||||||
qrCode = require('qrcode-npm'),
|
qrCode = require('qrcode-npm'),
|
||||||
util = require('./util'),
|
util = require('./util').default,
|
||||||
certMgr = require('./certMgr'),
|
certMgr = require('./certMgr'),
|
||||||
wsServer = require('./wsServer'),
|
wsServer = require('./wsServer'),
|
||||||
juicer = require('juicer'),
|
juicer = require('juicer'),
|
||||||
@ -56,7 +56,7 @@ class webInterface extends events.EventEmitter {
|
|||||||
*/
|
*/
|
||||||
getServer() {
|
getServer() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const recorder = self.recorder;
|
const recorder = self.recorder;
|
||||||
const ipAddress = ip.address(),
|
const ipAddress = ip.address(),
|
||||||
// userRule = proxyInstance.proxyRule,
|
// userRule = proxyInstance.proxyRule,
|
||||||
webBasePath = 'web';
|
webBasePath = 'web';
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^8.10.21",
|
"@types/node": "^8.10.21",
|
||||||
|
"@types/ws": "^6.0.0",
|
||||||
"antd": "^2.5.0",
|
"antd": "^2.5.0",
|
||||||
"autoprefixer": "^6.4.1",
|
"autoprefixer": "^6.4.1",
|
||||||
"babel-core": "^6.14.0",
|
"babel-core": "^6.14.0",
|
||||||
|
@ -9,14 +9,19 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
*beforeSendRequest(requestDetail) {
|
*beforeSendRequest(requestDetail) {
|
||||||
if (requestDetail.url.indexOf('https://httpbin.org/user-agent') === 0) {
|
if (requestDetail.url.indexOf('https://httpbin.org/user-agent') === 0) {
|
||||||
|
console.info(requestDetail._req.connection._peername);
|
||||||
const newRequestOptions = requestDetail.requestOptions;
|
const newRequestOptions = requestDetail.requestOptions;
|
||||||
requestDetail.protocol = 'http';
|
requestDetail.protocol = 'https';
|
||||||
newRequestOptions.hostname = '127.0.0.1'
|
newRequestOptions.hostname = '127.0.0.1'
|
||||||
newRequestOptions.port = '8008';
|
newRequestOptions.port = '3001';
|
||||||
newRequestOptions.path = '/index.html';
|
newRequestOptions.path = '/test';
|
||||||
newRequestOptions.method = 'GET';
|
newRequestOptions.method = 'GET';
|
||||||
return requestDetail;
|
return requestDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (requestDetail.url.indexOf('http://mobilegw.stable.alipay.net/mgw.htm') === 0) {
|
||||||
|
console.info(requestDetail.requestData.toString())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
*beforeDealHttpsRequest(requestDetail) {
|
*beforeDealHttpsRequest(requestDetail) {
|
||||||
return true;
|
return true;
|
||||||
|
2
test.js
2
test.js
@ -1,7 +1,7 @@
|
|||||||
const Jasmine = require('jasmine');
|
const Jasmine = require('jasmine');
|
||||||
|
|
||||||
const jasmine = new Jasmine();
|
const jasmine = new Jasmine();
|
||||||
const util = require('./lib/util');
|
const util = require('./lib/util').default;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const testTmpPath = path.join(__dirname, './test/temp');
|
const testTmpPath = path.join(__dirname, './test/temp');
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* test for rule replaceOption rule
|
* test for rule replaceOption rule
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const util = require('../../lib/util');
|
const util = require('../../lib/util').default;
|
||||||
|
|
||||||
describe('utils', () => {
|
describe('utils', () => {
|
||||||
it('should get some free ports', done => {
|
it('should get some free ports', done => {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"./dist"
|
"./dist"
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"./lib/*",
|
"./lib/**/*",
|
||||||
"./bin/*"
|
"./bin/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
48
tslint.json
Normal file
48
tslint.json
Normal file
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
typings/index.d.ts
vendored
74
typings/index.d.ts
vendored
@ -1,6 +1,21 @@
|
|||||||
declare interface AnyProxyConfig {
|
|
||||||
port: number
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
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 {
|
declare interface AnyProxyRule {
|
||||||
summary?: string,
|
summary?: string,
|
||||||
beforeSendRequest?: Function,
|
beforeSendRequest?: Function,
|
||||||
@ -8,4 +23,59 @@ declare interface AnyProxyRule {
|
|||||||
beforeDealHttpsRequest?: Function,
|
beforeDealHttpsRequest?: Function,
|
||||||
onError?: Function,
|
onError?: Function,
|
||||||
onConnectError?: Function
|
onConnectError?: Function
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace AnyProxyRecorder {
|
||||||
|
type ResponseHeader = any; // 暂时无法引入http模块,会导致
|
||||||
|
export type WsResourceInfo = {
|
||||||
|
time: number,
|
||||||
|
message: string,
|
||||||
|
isToServer: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResourceInfo = {
|
||||||
|
wsMessages?: Array<WsResourceInfo>,
|
||||||
|
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
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user