mirror of
https://github.com/alibaba/anyproxy.git
synced 2025-04-20 20:44:21 +00:00
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b93f948107 | ||
|
8f3e54b1c5 | ||
|
78858bfb94 | ||
|
5a1af37614 | ||
|
28108d4c78 | ||
|
d3c306b976 | ||
|
3e2daef4aa | ||
|
e003c887fa | ||
|
68188c9cd6 | ||
|
8951fb68ef | ||
|
f653718dc4 | ||
|
9e7eb6c7ad | ||
|
8ca35ba809 | ||
|
c9dea0cffd | ||
|
a3ebfdd164 | ||
|
faf8b03a67 | ||
|
54fa80ad81 | ||
|
d9f2e7528c | ||
|
bef9474885 | ||
|
a6ab1a5ce8 | ||
|
0927d98222 | ||
|
65f530047a | ||
|
1c3184b044 | ||
|
2f63cd96c1 | ||
|
01e0b01949 | ||
|
7f02664079 | ||
|
15d7ed48bf | ||
|
67151c071b | ||
|
ac16f87ce6 | ||
|
f900904a44 | ||
|
344dcf8f66 | ||
|
933cf25300 | ||
|
d9548ae7cf | ||
|
fc4befa209 | ||
|
9d3e4776a2 | ||
|
5958e770f2 | ||
|
db95da680d | ||
|
ed91608118 | ||
|
cb25f1eff1 | ||
|
3a310b0c0e | ||
|
763bdc07a2 | ||
|
c040ae4578 | ||
|
9a9c076554 | ||
|
a8c9f590fc | ||
|
59e05850fa | ||
|
d44ce7fd08 | ||
|
9682926e67 | ||
|
ce1327205c | ||
|
df6ab4baf7 | ||
|
0621dadf28 | ||
|
e7732049db | ||
|
0241b90e4d | ||
|
bf9b1cebe1 | ||
|
71477d5aae | ||
|
2e9e556e26 | ||
|
8937cc66a9 | ||
|
f271a8a248 | ||
|
cd8a725c11 | ||
|
cb428c9662 | ||
|
10f84d0f99 | ||
|
aae5c9b039 | ||
|
11e68100a4 | ||
|
e01bb38b80 | ||
|
1fd69b3e87 | ||
|
98fc3fa2ee | ||
|
a5027340dc | ||
|
310f84d68f | ||
|
1405356292 | ||
|
4244abe9e0 | ||
|
4d0a8207ff |
@ -5,7 +5,7 @@
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"jasmine": true
|
||||
"jest": true
|
||||
},
|
||||
"globals": {
|
||||
"React": true,
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -35,3 +35,5 @@ test/report
|
||||
doc_compiled.md
|
||||
docs-md/cn/doc.md
|
||||
docs-md/en/doc.md
|
||||
docs/gitbook/gitbook-plugin-livereload/
|
||||
node_modules
|
||||
|
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 12
|
||||
before_script:
|
||||
- node ./bin/anyproxy-ca -g
|
@ -4,10 +4,11 @@ AnyProxy
|
||||
[![NPM version][npm-image]][npm-url]
|
||||
[![node version][node-image]][node-url]
|
||||
[![npm download][download-image]][download-url]
|
||||
[](https://travis-ci.org/alibaba/anyproxy)
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/anyproxy.svg?style=flat-square
|
||||
[npm-url]: https://npmjs.org/package/anyproxy
|
||||
[node-image]: https://img.shields.io/badge/node.js-%3E=_5.0.0-green.svg?style=flat-square
|
||||
[node-image]: https://img.shields.io/badge/node.js-%3E=_6.0.0-green.svg?style=flat-square
|
||||
[node-url]: http://nodejs.org/download/
|
||||
[download-image]: https://img.shields.io/npm/dm/anyproxy.svg?style=flat-square
|
||||
[download-url]: https://npmjs.org/package/anyproxy
|
||||
@ -20,7 +21,7 @@ Issue: https://github.com/alibaba/anyproxy/issues
|
||||
|
||||
AnyProxy是一个基于NodeJS的,可供插件配置的HTTP/HTTPS代理服务器。
|
||||
|
||||
主页:[AnyProxy.io](http://anyproxy.io)
|
||||
主页:[AnyProxy.io](http://anyproxy.io),访问可能需要稳定的国际网络环境
|
||||
|
||||

|
||||
|
||||
|
11
babel.config.js
Normal file
11
babel.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
module.exports = {};
|
||||
} else {
|
||||
module.exports = {
|
||||
presets: [
|
||||
'es2015',
|
||||
'stage-0'
|
||||
]
|
||||
};
|
||||
}
|
||||
|
85
bin/anyproxy
85
bin/anyproxy
@ -4,9 +4,11 @@
|
||||
|
||||
const program = require('commander'),
|
||||
color = require('colorful'),
|
||||
co = require('co'),
|
||||
packageInfo = require('../package.json'),
|
||||
ruleLoader = require('../lib/ruleLoader'),
|
||||
util = require('../lib/util'),
|
||||
rootCACheck = require('./rootCACheck'),
|
||||
startServer = require('./startServer'),
|
||||
logUtil = require('../lib/log');
|
||||
|
||||
program
|
||||
@ -24,7 +26,7 @@ program
|
||||
|
||||
if (program.clear) {
|
||||
require('../lib/certMgr').clearCerts(() => {
|
||||
util.deleteFolderContentsRecursive(util.getAnyProxyPath('cache'));
|
||||
util.deleteFolderContentsRecursive(util.getAnyProxyTmpPath());
|
||||
console.log(color.green('done !'));
|
||||
process.exit(0);
|
||||
});
|
||||
@ -33,85 +35,20 @@ if (program.clear) {
|
||||
process.exit(0);
|
||||
});
|
||||
} else {
|
||||
const AnyProxy = require('../proxy.js');
|
||||
let proxyServer;
|
||||
|
||||
co(function *() {
|
||||
if (program.silent) {
|
||||
logUtil.setPrintStatus(false);
|
||||
}
|
||||
|
||||
// load rule module
|
||||
new Promise((resolve, reject) => {
|
||||
if (program.rule) {
|
||||
resolve(ruleLoader.requireModule(program.rule));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
logUtil.printLog('Failed to load rule file', logUtil.T_ERR);
|
||||
logUtil.printLog(e, logUtil.T_ERR);
|
||||
process.exit();
|
||||
})
|
||||
|
||||
//start proxy
|
||||
.then(ruleModule => {
|
||||
proxyServer = new AnyProxy.ProxyServer({
|
||||
type: 'http',
|
||||
port: program.port || 8001,
|
||||
throttle: program.throttle,
|
||||
rule: ruleModule,
|
||||
webInterface: {
|
||||
enable: true,
|
||||
webPort: program.web,
|
||||
},
|
||||
wsIntercept: program.wsIntercept,
|
||||
forceProxyHttps: program.intercept,
|
||||
dangerouslyIgnoreUnauthorized: !!program.ignoreUnauthorizedSsl,
|
||||
silent: program.silent
|
||||
});
|
||||
// proxyServer.on('ready', () => {});
|
||||
proxyServer.start();
|
||||
})
|
||||
.catch(e => {
|
||||
logUtil.printLog(e, logUtil.T_ERR);
|
||||
if (e && e.code) {
|
||||
logUtil.printLog('code ' + e.code, logUtil.T_ERR);
|
||||
}
|
||||
logUtil.printLog(e.stack, logUtil.T_ERR);
|
||||
});
|
||||
|
||||
process.on('exit', (code) => {
|
||||
if (code > 0) {
|
||||
logUtil.printLog('AnyProxy is about to exit with code: ' + code, logUtil.T_ERR);
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
|
||||
//exit cause ctrl+c
|
||||
process.on('SIGINT', () => {
|
||||
if (program.intercept) {
|
||||
try {
|
||||
proxyServer && proxyServer.close();
|
||||
yield rootCACheck();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
let errorTipText = 'got an uncaught exception, is there anything goes wrong in your rule file ?\n';
|
||||
try {
|
||||
if (err && err.stack) {
|
||||
errorTipText += err.stack;
|
||||
} else {
|
||||
errorTipText += err;
|
||||
}
|
||||
} catch (e) {}
|
||||
logUtil.printLog(errorTipText, logUtil.T_ERR);
|
||||
try {
|
||||
proxyServer && proxyServer.close();
|
||||
} catch (e) {}
|
||||
process.exit();
|
||||
});
|
||||
return startServer(program);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -20,10 +20,10 @@ program
|
||||
.parse(process.argv);
|
||||
|
||||
function openFolderOfFile(filePath) {
|
||||
const isWin = /^win/.test(process.platform);
|
||||
if (isWin) {
|
||||
const platform = process.platform;
|
||||
if (/^win/.test(platform)) {
|
||||
exec('start .', { cwd: path.dirname(filePath) });
|
||||
} else {
|
||||
} else if (/darwin/.test(platform)) {
|
||||
exec(`open -R ${filePath}`);
|
||||
}
|
||||
}
|
||||
@ -33,7 +33,6 @@ function guideToGenrateCA() {
|
||||
if (!error) {
|
||||
const certDir = path.dirname(keyPath);
|
||||
console.log(`The cert is generated at ${certDir}. Please trust the ${color.bold('rootCA.crt')}.`);
|
||||
// TODO: console.log('guide to install');
|
||||
openFolderOfFile(crtPath);
|
||||
} else {
|
||||
console.error('failed to generate rootCA', error);
|
||||
@ -44,7 +43,6 @@ function guideToGenrateCA() {
|
||||
function guideToTrustCA() {
|
||||
const certPath = AnyProxy.utils.certMgr.getRootCAFilePath();
|
||||
if (certPath) {
|
||||
// TODO: console.log('guide to install');
|
||||
openFolderOfFile(certPath);
|
||||
} else {
|
||||
console.error('failed to get cert path');
|
||||
|
33
bin/rootCACheck.js
Normal file
33
bin/rootCACheck.js
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* check if root CA exists and installed
|
||||
* will prompt to generate when needed
|
||||
*/
|
||||
|
||||
const thunkify = require('thunkify');
|
||||
const AnyProxy = require('../proxy');
|
||||
const logUtil = require('../lib/log');
|
||||
|
||||
const certMgr = AnyProxy.utils.certMgr;
|
||||
|
||||
function checkRootCAExists() {
|
||||
return certMgr.isRootCAFileExists();
|
||||
}
|
||||
|
||||
module.exports = function *() {
|
||||
try {
|
||||
if (!checkRootCAExists()) {
|
||||
logUtil.warn('Missing root CA, generating now');
|
||||
yield thunkify(certMgr.generateRootCA)();
|
||||
yield certMgr.trustRootCA();
|
||||
} else {
|
||||
const isCATrusted = yield thunkify(certMgr.ifRootCATrusted)();
|
||||
if (!isCATrusted) {
|
||||
logUtil.warn('ROOT CA NOT INSTALLED YET');
|
||||
yield certMgr.trustRootCA();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
86
bin/startServer.js
Normal file
86
bin/startServer.js
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* start the AnyProxy server
|
||||
*/
|
||||
|
||||
const ruleLoader = require('../lib/ruleLoader');
|
||||
const logUtil = require('../lib/log');
|
||||
const AnyProxy = require('../proxy');
|
||||
|
||||
module.exports = function startServer(program) {
|
||||
let proxyServer;
|
||||
// load rule module
|
||||
new Promise((resolve, reject) => {
|
||||
if (program.rule) {
|
||||
resolve(ruleLoader.requireModule(program.rule));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
logUtil.printLog('Failed to load rule file', logUtil.T_ERR);
|
||||
logUtil.printLog(e, logUtil.T_ERR);
|
||||
process.exit();
|
||||
})
|
||||
|
||||
//start proxy
|
||||
.then(ruleModule => {
|
||||
proxyServer = new AnyProxy.ProxyServer({
|
||||
type: 'http',
|
||||
port: program.port || 8001,
|
||||
throttle: program.throttle,
|
||||
rule: ruleModule,
|
||||
webInterface: {
|
||||
enable: true,
|
||||
webPort: program.web,
|
||||
},
|
||||
wsIntercept: program.wsIntercept,
|
||||
forceProxyHttps: program.intercept,
|
||||
dangerouslyIgnoreUnauthorized: !!program.ignoreUnauthorizedSsl,
|
||||
silent: program.silent
|
||||
});
|
||||
// proxyServer.on('ready', () => {});
|
||||
proxyServer.start();
|
||||
})
|
||||
.catch(e => {
|
||||
logUtil.printLog(e, logUtil.T_ERR);
|
||||
if (e && e.code) {
|
||||
logUtil.printLog('code ' + e.code, logUtil.T_ERR);
|
||||
}
|
||||
logUtil.printLog(e.stack, logUtil.T_ERR);
|
||||
});
|
||||
|
||||
|
||||
process.on('exit', (code) => {
|
||||
if (code > 0) {
|
||||
logUtil.printLog('AnyProxy is about to exit with code: ' + code, logUtil.T_ERR);
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
|
||||
//exit cause ctrl+c
|
||||
process.on('SIGINT', () => {
|
||||
try {
|
||||
proxyServer && proxyServer.close();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
process.exit();
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
let errorTipText = 'got an uncaught exception, is there anything goes wrong in your rule file ?\n';
|
||||
try {
|
||||
if (err && err.stack) {
|
||||
errorTipText += err.stack;
|
||||
} else {
|
||||
errorTipText += err;
|
||||
}
|
||||
} catch (e) { }
|
||||
logUtil.printLog(errorTipText, logUtil.T_ERR);
|
||||
try {
|
||||
proxyServer && proxyServer.close();
|
||||
} catch (e) { }
|
||||
process.exit();
|
||||
});
|
||||
}
|
@ -9,6 +9,6 @@ node ./build_scripts/prebuild-doc.js
|
||||
gitbook build ./docs-src ./docs
|
||||
|
||||
## push the doc into github
|
||||
git add ./docs
|
||||
git commit -m 'building docs'
|
||||
git push origin
|
||||
# git add ./docs
|
||||
# git commit -m 'building docs'
|
||||
# git push origin
|
||||
|
@ -79,11 +79,11 @@ const options = {
|
||||
rule: require('myRuleModule'),
|
||||
webInterface: {
|
||||
enable: true,
|
||||
webPort: 8002,
|
||||
wsPort: 8003,
|
||||
webPort: 8002
|
||||
},
|
||||
throttle: 10000,
|
||||
forceProxyHttps: false,
|
||||
wsIntercept: false, // 不开启websocket代理
|
||||
silent: false
|
||||
};
|
||||
const proxyServer = new AnyProxy.ProxyServer(options);
|
||||
@ -110,6 +110,7 @@ proxyServer.close();
|
||||
* `forceProxyHttps` {boolean} 是否强制拦截所有的https,忽略规则模块的返回,默认`false`
|
||||
* `silent` {boolean} 是否屏蔽所有console输出,默认`false`
|
||||
* `dangerouslyIgnoreUnauthorized` {boolean} 是否忽略请求中的证书错误,默认`false`
|
||||
* `wsIntercept` {boolean} 是否开启websocket代理,默认`false`
|
||||
* `webInterface` {object} web版界面配置
|
||||
* `enable` {boolean} 是否启用web版界面,默认`false`
|
||||
* `webPort` {number} web版界面端口号,默认`8002`
|
||||
@ -163,7 +164,7 @@ proxyServer.close();
|
||||
* 样例
|
||||
|
||||
```js
|
||||
const AnyProxy = require('AnyProxy');
|
||||
const AnyProxy = require('anyproxy');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
|
||||
@ -199,6 +200,14 @@ anyproxy --intercept #启动AnyProxy,并解析所有https请求
|
||||
|
||||
* [附录:如何信任CA证书](#证书配置)
|
||||
|
||||
# 代理WebSocket
|
||||
|
||||
```bash
|
||||
anyproxy --ws-intercept
|
||||
```
|
||||
|
||||
> 当启用`HTTPS`代理时,`wss`也会被代理,但是不会被AnyProxy记录。需要开启`--ws-intercept`后才会从界面上看到相应内容。
|
||||
|
||||
# rule模块
|
||||
|
||||
AnyProxy提供了二次开发的能力,你可以用js编写自己的规则模块(rule),来自定义网络请求的处理逻辑。
|
||||
@ -818,6 +827,8 @@ module.exports = {
|
||||
* 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装
|
||||
* 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件,进行安装
|
||||
|
||||
不同安卓系统支持安装的证书文件类型不尽相同,大多支持安装拓展名为 .crt 的证书文件,少部分仅支持 .cer 文件(已知如 OPPO R15),AnyProxy 提供了多种类型的证书文件,可在下载安装时选择。
|
||||
|
||||
### 配置iOS/Android系统代理
|
||||
|
||||
* 代理服务器都在wifi设置中配置
|
||||
|
@ -8,6 +8,7 @@
|
||||
* [其他命令](README.md#其他命令)
|
||||
* [作为npm模块启动](README.md#作为npm模块使用)
|
||||
* [代理HTTPS](README.md#代理https)
|
||||
* [代理WebSocket](README.md#代理websocket)
|
||||
* [rule模块](README.md#rule模块)
|
||||
* [开发示例](README.md#开发示例)
|
||||
* [处理流程](README.md#处理流程)
|
||||
|
@ -164,7 +164,7 @@ proxyServer.close();
|
||||
* 样例
|
||||
|
||||
```js
|
||||
const AnyProxy = require('AnyProxy');
|
||||
const AnyProxy = require('anyproxy');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
|
||||
@ -200,6 +200,14 @@ anyproxy --intercept #启动AnyProxy,并解析所有https请求
|
||||
|
||||
* [附录:如何信任CA证书](#证书配置)
|
||||
|
||||
# 代理WebSocket
|
||||
|
||||
```bash
|
||||
anyproxy --ws-intercept
|
||||
```
|
||||
|
||||
> 当启用`HTTPS`代理时,`wss`也会被代理,但是不会被AnyProxy记录。需要开启`--ws-intercept`后才会从界面上看到相应内容。
|
||||
|
||||
# rule模块
|
||||
|
||||
AnyProxy提供了二次开发的能力,你可以用js编写自己的规则模块(rule),来自定义网络请求的处理逻辑。
|
||||
@ -620,6 +628,8 @@ module.exports = {
|
||||
* 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装
|
||||
* 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件,进行安装
|
||||
|
||||
不同安卓系统支持安装的证书文件类型不尽相同,大多支持安装拓展名为 .crt 的证书文件,少部分仅支持 .cer 文件(已知如 OPPO R15),AnyProxy 提供了多种类型的证书文件,可在下载安装时选择。
|
||||
|
||||
### 配置iOS/Android系统代理
|
||||
|
||||
* 代理服务器都在wifi设置中配置
|
||||
|
@ -78,11 +78,11 @@ const options = {
|
||||
rule: require('myRuleModule'),
|
||||
webInterface: {
|
||||
enable: true,
|
||||
webPort: 8002,
|
||||
wsPort: 8003,
|
||||
webPort: 8002
|
||||
},
|
||||
throttle: 10000,
|
||||
forceProxyHttps: false,
|
||||
wsIntercept: false,
|
||||
silent: false
|
||||
};
|
||||
const proxyServer = new AnyProxy.ProxyServer(options);
|
||||
@ -106,11 +106,12 @@ proxyServer.close();
|
||||
* `port` {number} required, port number of proxy server
|
||||
* `rule` {object} your rule module
|
||||
* `throttle` {number} throttle in kb/s, unlimited for default
|
||||
* `forceProxyHttps` {boolean} in force intercept all https request, false for default
|
||||
* `forceProxyHttps` {boolean} in force intercept all https request, default to `false`
|
||||
* `silent` {boolean} if keep silent in console, false for default `false`
|
||||
* `dangerouslyIgnoreUnauthorized` {boolean} if ignore certificate error in request, false for default
|
||||
* `dangerouslyIgnoreUnauthorized` {boolean} if ignore certificate error in request, default to `false`
|
||||
* `wsIntercept` {boolean} whether to intercept websocket, default to `false`
|
||||
* `webInterface` {object} config for web interface
|
||||
* `enable` {boolean} if enable web interface, false for default
|
||||
* `enable` {boolean} if enable web interface, default to `false`
|
||||
* `webPort` {number} port number for web interface
|
||||
* Event: `ready`
|
||||
* emit when proxy server is ready
|
||||
@ -162,7 +163,7 @@ proxyServer.close();
|
||||
* Sample
|
||||
|
||||
```js
|
||||
const AnyProxy = require('AnyProxy');
|
||||
const AnyProxy = require('anyproxy');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
|
||||
@ -199,6 +200,13 @@ anyproxy --intercept #launch anyproxy and intercept all https traffic
|
||||
|
||||
* [Appendix:how to trust CA](#config-certification)
|
||||
|
||||
# Proxy WebSocket
|
||||
|
||||
```bash
|
||||
anyproxy --ws-intercept
|
||||
```
|
||||
> The `wss` requests will be handled automatically when the `HTTPS` intercept is turned on, but AnyProxy will not record the data by default. You need to specify the `--ws-intercept` to tell AnyProxy to record it.
|
||||
|
||||
# Rule Introduction
|
||||
|
||||
AnyProxy provides the ability to load your own rules written in javascript. With rule module, you could customize the logic to handle requests.
|
||||
@ -801,6 +809,8 @@ install :
|
||||
|
||||
* Besides installing root CA, you have to "turn on" the certificate for web manually in *settings - general - about - Certificate Trust Settings*. Otherwire, safari will not trust the root CA generated by AnyProxy.
|
||||
|
||||
<img src="https://zos.alipayobjects.com/rmsportal/hVWkXHrzHmOKOtCKGUWx.png" width="500" />
|
||||
|
||||
### trust root CA in Android
|
||||
First of all, you need to download the root CA by clicking *Root CA* in web ui, and then scan the QR code.
|
||||
Installing CA in Android could be different based on the system, we list some common steps as below, but you can find the right way in you system with similar menu path.
|
||||
@ -810,7 +820,7 @@ Installing CA in Android could be different based on the system, we list some co
|
||||
* Settings -> Security & Location > Encryption & credentials -> Install from storage, and find your CA file to install
|
||||
* Settings -> Security -> Install from SD card, and find you CA file to install
|
||||
|
||||
<img src="https://zos.alipayobjects.com/rmsportal/hVWkXHrzHmOKOtCKGUWx.png" width="500" />
|
||||
There are several file extensions of CA file which may not be compatible with all kinds of Android phones. `.crt` file is the most popular, while a few systems could only use .cer file such as OPPO R15. In AnyProxy, you can choose the type of certificate you need before installing.
|
||||
|
||||
### config iOS/Android proxy server
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
* [Options](README.md#options)
|
||||
* [As Node Module](README.md#use-anyproxy-as-an-npm-module)
|
||||
* [Proxy HTTPS](README.md#proxy-https)
|
||||
* [Proxy WebSocket](README.md#proxy-websocket)
|
||||
* [Rule Introduction](README.md#rule-introduction)
|
||||
* [Sample](README.md#sample)
|
||||
* [How Does It Work](README.md#how-does-it-work)
|
||||
|
@ -163,7 +163,7 @@ proxyServer.close();
|
||||
* Sample
|
||||
|
||||
```js
|
||||
const AnyProxy = require('AnyProxy');
|
||||
const AnyProxy = require('anyproxy');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
|
||||
@ -200,6 +200,13 @@ anyproxy --intercept #launch anyproxy and intercept all https traffic
|
||||
|
||||
* [Appendix:how to trust CA](#config-certification)
|
||||
|
||||
# Proxy WebSocket
|
||||
|
||||
```bash
|
||||
anyproxy --ws-intercept
|
||||
```
|
||||
> The `wss` requests will be handled automatically when the `HTTPS` intercept is turned on, but AnyProxy will not record the data by default. You need to specify the `--ws-intercept` to tell AnyProxy to record it.
|
||||
|
||||
# Rule Introduction
|
||||
|
||||
AnyProxy provides the ability to load your own rules written in javascript. With rule module, you could customize the logic to handle requests.
|
||||
@ -603,6 +610,8 @@ install :
|
||||
|
||||
* Besides installing root CA, you have to "turn on" the certificate for web manually in *settings - general - about - Certificate Trust Settings*. Otherwire, safari will not trust the root CA generated by AnyProxy.
|
||||
|
||||
<img src="https://zos.alipayobjects.com/rmsportal/hVWkXHrzHmOKOtCKGUWx.png" width="500" />
|
||||
|
||||
### trust root CA in Android
|
||||
First of all, you need to download the root CA by clicking *Root CA* in web ui, and then scan the QR code.
|
||||
Installing CA in Android could be different based on the system, we list some common steps as below, but you can find the right way in you system with similar menu path.
|
||||
@ -612,7 +621,7 @@ Installing CA in Android could be different based on the system, we list some co
|
||||
* Settings -> Security & Location > Encryption & credentials -> Install from storage, and find your CA file to install
|
||||
* Settings -> Security -> Install from SD card, and find you CA file to install
|
||||
|
||||
<img src="https://zos.alipayobjects.com/rmsportal/hVWkXHrzHmOKOtCKGUWx.png" width="500" />
|
||||
There are several file extensions of CA file which may not be compatible with all kinds of Android phones. `.crt` file is the most popular, while a few systems could only use .cer file such as OPPO R15. In AnyProxy, you can choose the type of certificate you need before installing.
|
||||
|
||||
### config iOS/Android proxy server
|
||||
|
||||
|
@ -198,6 +198,22 @@
|
||||
|
||||
<li class="chapter " data-level="1.4" data-path="./">
|
||||
|
||||
<a href="./#代理websocket">
|
||||
|
||||
|
||||
<div class="summary-title-span 代理WebSocket">
|
||||
代理WebSocket
|
||||
</div>
|
||||
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5" data-path="./">
|
||||
|
||||
<a href="./#rule模块">
|
||||
|
||||
|
||||
@ -213,7 +229,7 @@
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.4.1" data-path="./">
|
||||
<li class="chapter " data-level="1.5.1" data-path="./">
|
||||
|
||||
<a href="./#开发示例">
|
||||
|
||||
@ -229,7 +245,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4.2" data-path="./">
|
||||
<li class="chapter " data-level="1.5.2" data-path="./">
|
||||
|
||||
<a href="./#处理流程">
|
||||
|
||||
@ -245,7 +261,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4.3" data-path="./">
|
||||
<li class="chapter " data-level="1.5.3" data-path="./">
|
||||
|
||||
<a href="./#如何引用">
|
||||
|
||||
@ -266,7 +282,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5" data-path="./">
|
||||
<li class="chapter " data-level="1.6" data-path="./">
|
||||
|
||||
<a href="./#rule接口文档">
|
||||
|
||||
@ -283,7 +299,7 @@
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.5.1" data-path="./">
|
||||
<li class="chapter " data-level="1.6.1" data-path="./">
|
||||
|
||||
<a href="./#summary">
|
||||
|
||||
@ -299,7 +315,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.2" data-path="./">
|
||||
<li class="chapter " data-level="1.6.2" data-path="./">
|
||||
|
||||
<a href="./#beforesendrequest">
|
||||
|
||||
@ -315,7 +331,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.3" data-path="./">
|
||||
<li class="chapter " data-level="1.6.3" data-path="./">
|
||||
|
||||
<a href="./#beforesendresponse">
|
||||
|
||||
@ -331,7 +347,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.4" data-path="./">
|
||||
<li class="chapter " data-level="1.6.4" data-path="./">
|
||||
|
||||
<a href="./#beforedealhttpsrequest">
|
||||
|
||||
@ -347,7 +363,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.5" data-path="./">
|
||||
<li class="chapter " data-level="1.6.5" data-path="./">
|
||||
|
||||
<a href="./#onerror">
|
||||
|
||||
@ -363,7 +379,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.6" data-path="./">
|
||||
<li class="chapter " data-level="1.6.6" data-path="./">
|
||||
|
||||
<a href="./#onconnecterror">
|
||||
|
||||
@ -384,7 +400,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6" data-path="./">
|
||||
<li class="chapter " data-level="1.7" data-path="./">
|
||||
|
||||
<a href="./#rule样例">
|
||||
|
||||
@ -401,7 +417,7 @@
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.6.1" data-path="./">
|
||||
<li class="chapter " data-level="1.7.1" data-path="./">
|
||||
|
||||
<a href="./#使用本地数据">
|
||||
|
||||
@ -417,7 +433,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.2" data-path="./">
|
||||
<li class="chapter " data-level="1.7.2" data-path="./">
|
||||
|
||||
<a href="./#修改请求头">
|
||||
|
||||
@ -433,7 +449,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.3" data-path="./">
|
||||
<li class="chapter " data-level="1.7.3" data-path="./">
|
||||
|
||||
<a href="./#修改请求数据">
|
||||
|
||||
@ -449,7 +465,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.4" data-path="./">
|
||||
<li class="chapter " data-level="1.7.4" data-path="./">
|
||||
|
||||
<a href="./#修改请求的目标地址">
|
||||
|
||||
@ -465,7 +481,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.5" data-path="./">
|
||||
<li class="chapter " data-level="1.7.5" data-path="./">
|
||||
|
||||
<a href="./#修改请求协议">
|
||||
|
||||
@ -481,7 +497,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.6" data-path="./">
|
||||
<li class="chapter " data-level="1.7.6" data-path="./">
|
||||
|
||||
<a href="./#修改返回状态码">
|
||||
|
||||
@ -497,7 +513,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.7" data-path="./">
|
||||
<li class="chapter " data-level="1.7.7" data-path="./">
|
||||
|
||||
<a href="./#修改返回头">
|
||||
|
||||
@ -513,7 +529,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.8" data-path="./">
|
||||
<li class="chapter " data-level="1.7.8" data-path="./">
|
||||
|
||||
<a href="./#修改返回内容并延迟">
|
||||
|
||||
@ -534,7 +550,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7" data-path="./">
|
||||
<li class="chapter " data-level="1.8" data-path="./">
|
||||
|
||||
<a href="./#证书配置">
|
||||
|
||||
@ -551,7 +567,7 @@
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.7.1" data-path="./">
|
||||
<li class="chapter " data-level="1.8.1" data-path="./">
|
||||
|
||||
<a href="./#osx系统信任ca证书">
|
||||
|
||||
@ -567,7 +583,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.2" data-path="./">
|
||||
<li class="chapter " data-level="1.8.2" data-path="./">
|
||||
|
||||
<a href="./#windows系统信任ca证书">
|
||||
|
||||
@ -583,7 +599,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.3" data-path="./">
|
||||
<li class="chapter " data-level="1.8.3" data-path="./">
|
||||
|
||||
<a href="./#配置osx系统代理">
|
||||
|
||||
@ -599,7 +615,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.4" data-path="./">
|
||||
<li class="chapter " data-level="1.8.4" data-path="./">
|
||||
|
||||
<a href="./#配置浏览器http代理">
|
||||
|
||||
@ -615,7 +631,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.5" data-path="./">
|
||||
<li class="chapter " data-level="1.8.5" data-path="./">
|
||||
|
||||
<a href="./#ios系统信任ca证书">
|
||||
|
||||
@ -631,7 +647,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.6" data-path="./">
|
||||
<li class="chapter " data-level="1.8.6" data-path="./">
|
||||
|
||||
<a href="./#ios--103信任ca证书">
|
||||
|
||||
@ -647,7 +663,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.7" data-path="./">
|
||||
<li class="chapter " data-level="1.8.7" data-path="./">
|
||||
|
||||
<a href="./#安卓系统信任ca证书">
|
||||
|
||||
@ -663,7 +679,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.8" data-path="./">
|
||||
<li class="chapter " data-level="1.8.8" data-path="./">
|
||||
|
||||
<a href="./#配置iosandroid系统代理">
|
||||
|
||||
@ -684,7 +700,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.8" data-path="./">
|
||||
<li class="chapter " data-level="1.9" data-path="./">
|
||||
|
||||
<a href="./#faq">
|
||||
|
||||
@ -907,7 +923,7 @@ AnyProxy.utils.systemProxyMgr.disableGlobalProxy();
|
||||
</li>
|
||||
<li>样例</li>
|
||||
</ul>
|
||||
<pre><code class="lang-js"> <span class="hljs-keyword">const</span> AnyProxy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'AnyProxy'</span>);
|
||||
<pre><code class="lang-js"> <span class="hljs-keyword">const</span> AnyProxy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'anyproxy'</span>);
|
||||
<span class="hljs-keyword">const</span> exec = <span class="hljs-built_in">require</span>(<span class="hljs-string">'child_process'</span>).exec;
|
||||
|
||||
<span class="hljs-keyword">if</span> (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
|
||||
@ -946,6 +962,12 @@ anyproxy --intercept <span class="hljs-comment">#启动AnyProxy,
|
||||
<ul>
|
||||
<li><a href="#证书配置">附录:如何信任CA证书</a></li>
|
||||
</ul>
|
||||
<h1 id="代理websocket">代理WebSocket</h1>
|
||||
<pre><code class="lang-bash">anyproxy --ws-intercept
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
<p>当启用<code>HTTPS</code>代理时,<code>wss</code>也会被代理,但是不会被AnyProxy记录。需要开启<code>--ws-intercept</code>后才会从界面上看到相应内容。</p>
|
||||
</blockquote>
|
||||
<h1 id="rule模块">rule模块</h1>
|
||||
<p>AnyProxy提供了二次开发的能力,你可以用js编写自己的规则模块(rule),来自定义网络请求的处理逻辑。</p>
|
||||
<blockquote>
|
||||
@ -1539,6 +1561,7 @@ newResponse.body += <span class="hljs-string">'--from anyproxy--'</spa
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>不同安卓系统支持安装的证书文件类型不尽相同,大多支持安装拓展名为 .crt 的证书文件,少部分仅支持 .cer 文件(已知如 OPPO R15),AnyProxy 提供了多种类型的证书文件,可在下载安装时选择。</p>
|
||||
<h3 id="配置iosandroid系统代理">配置iOS/Android系统代理</h3>
|
||||
<ul>
|
||||
<li><p>代理服务器都在wifi设置中配置</p>
|
||||
@ -1636,7 +1659,7 @@ newResponse.body += <span class="hljs-string">'--from anyproxy--'</spa
|
||||
<script>
|
||||
var gitbook = gitbook || [];
|
||||
gitbook.push(function() {
|
||||
gitbook.page.hasChanged({"page":{"title":"简介","level":"1.1","depth":1,"next":{"title":"快速开始","level":"1.2","depth":1,"anchor":"#快速开始","path":"README.md","ref":"README.md#快速开始","articles":[{"title":"安装","level":"1.2.1","depth":2,"anchor":"#安装","path":"README.md","ref":"README.md#安装","articles":[]},{"title":"启动","level":"1.2.2","depth":2,"anchor":"#启动","path":"README.md","ref":"README.md#启动","articles":[]},{"title":"其他命令","level":"1.2.3","depth":2,"anchor":"#其他命令","path":"README.md","ref":"README.md#其他命令","articles":[]},{"title":"作为npm模块启动","level":"1.2.4","depth":2,"anchor":"#作为npm模块使用","path":"README.md","ref":"README.md#作为npm模块使用","articles":[]}]},"dir":"ltr"},"config":{"plugins":[],"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"AnyProxy","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"AnyProxy","language":"cn","gitbook":"*","description":"A fully configurable http/https proxy in NodeJS"},"file":{"path":"README.md","mtime":"2018-02-05T02:16:34.020Z","type":"markdown"},"gitbook":{"version":"3.2.2","time":"2018-02-05T02:16:35.891Z"},"basePath":".","book":{"language":"cn"}});
|
||||
gitbook.page.hasChanged({"page":{"title":"简介","level":"1.1","depth":1,"next":{"title":"快速开始","level":"1.2","depth":1,"anchor":"#快速开始","path":"README.md","ref":"README.md#快速开始","articles":[{"title":"安装","level":"1.2.1","depth":2,"anchor":"#安装","path":"README.md","ref":"README.md#安装","articles":[]},{"title":"启动","level":"1.2.2","depth":2,"anchor":"#启动","path":"README.md","ref":"README.md#启动","articles":[]},{"title":"其他命令","level":"1.2.3","depth":2,"anchor":"#其他命令","path":"README.md","ref":"README.md#其他命令","articles":[]},{"title":"作为npm模块启动","level":"1.2.4","depth":2,"anchor":"#作为npm模块使用","path":"README.md","ref":"README.md#作为npm模块使用","articles":[]}]},"dir":"ltr"},"config":{"plugins":[],"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"livereload":{},"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"AnyProxy","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"AnyProxy","language":"cn","gitbook":"*","description":"A fully configurable http/https proxy in NodeJS"},"file":{"path":"README.md","mtime":"2019-03-26T12:49:35.827Z","type":"markdown"},"gitbook":{"version":"3.2.2","time":"2019-03-26T12:49:36.852Z"},"basePath":".","book":{"language":"cn"}});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
@ -1646,6 +1669,10 @@ newResponse.body += <span class="hljs-string">'--from anyproxy--'</spa
|
||||
<script src="../gitbook/theme.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-livereload/plugin.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -164,7 +164,7 @@ proxyServer.close();
|
||||
* 样例
|
||||
|
||||
```js
|
||||
const AnyProxy = require('AnyProxy');
|
||||
const AnyProxy = require('anyproxy');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
|
||||
@ -200,6 +200,14 @@ anyproxy --intercept #启动AnyProxy,并解析所有https请求
|
||||
|
||||
* [附录:如何信任CA证书](#证书配置)
|
||||
|
||||
# 代理WebSocket
|
||||
|
||||
```bash
|
||||
anyproxy --ws-intercept
|
||||
```
|
||||
|
||||
> 当启用`HTTPS`代理时,`wss`也会被代理,但是不会被AnyProxy记录。需要开启`--ws-intercept`后才会从界面上看到相应内容。
|
||||
|
||||
# rule模块
|
||||
|
||||
AnyProxy提供了二次开发的能力,你可以用js编写自己的规则模块(rule),来自定义网络请求的处理逻辑。
|
||||
@ -620,6 +628,8 @@ module.exports = {
|
||||
* 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装
|
||||
* 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件,进行安装
|
||||
|
||||
不同安卓系统支持安装的证书文件类型不尽相同,大多支持安装拓展名为 .crt 的证书文件,少部分仅支持 .cer 文件(已知如 OPPO R15),AnyProxy 提供了多种类型的证书文件,可在下载安装时选择。
|
||||
|
||||
### 配置iOS/Android系统代理
|
||||
|
||||
* 代理服务器都在wifi设置中配置
|
||||
|
@ -185,6 +185,22 @@
|
||||
|
||||
<li class="chapter " data-level="1.4" data-path="./">
|
||||
|
||||
<a href="./#proxy-websocket">
|
||||
|
||||
|
||||
<div class="summary-title-span Proxy WebSocket">
|
||||
Proxy WebSocket
|
||||
</div>
|
||||
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5" data-path="./">
|
||||
|
||||
<a href="./#rule-introduction">
|
||||
|
||||
|
||||
@ -200,7 +216,7 @@
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.4.1" data-path="./">
|
||||
<li class="chapter " data-level="1.5.1" data-path="./">
|
||||
|
||||
<a href="./#sample">
|
||||
|
||||
@ -216,7 +232,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4.2" data-path="./">
|
||||
<li class="chapter " data-level="1.5.2" data-path="./">
|
||||
|
||||
<a href="./#how-does-it-work">
|
||||
|
||||
@ -232,7 +248,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4.3" data-path="./">
|
||||
<li class="chapter " data-level="1.5.3" data-path="./">
|
||||
|
||||
<a href="./#how-to-load-rule-module">
|
||||
|
||||
@ -253,7 +269,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5" data-path="./">
|
||||
<li class="chapter " data-level="1.6" data-path="./">
|
||||
|
||||
<a href="./#rule-module-interface">
|
||||
|
||||
@ -270,7 +286,7 @@
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.5.1" data-path="./">
|
||||
<li class="chapter " data-level="1.6.1" data-path="./">
|
||||
|
||||
<a href="./#summary">
|
||||
|
||||
@ -286,7 +302,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.2" data-path="./">
|
||||
<li class="chapter " data-level="1.6.2" data-path="./">
|
||||
|
||||
<a href="./#beforesendrequest">
|
||||
|
||||
@ -302,7 +318,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.3" data-path="./">
|
||||
<li class="chapter " data-level="1.6.3" data-path="./">
|
||||
|
||||
<a href="./#beforesendresponse">
|
||||
|
||||
@ -318,7 +334,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.4" data-path="./">
|
||||
<li class="chapter " data-level="1.6.4" data-path="./">
|
||||
|
||||
<a href="./#beforedealhttpsrequest">
|
||||
|
||||
@ -334,7 +350,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.5" data-path="./">
|
||||
<li class="chapter " data-level="1.6.5" data-path="./">
|
||||
|
||||
<a href="./#onerror">
|
||||
|
||||
@ -350,7 +366,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.6" data-path="./">
|
||||
<li class="chapter " data-level="1.6.6" data-path="./">
|
||||
|
||||
<a href="./#onconnecterror">
|
||||
|
||||
@ -371,7 +387,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6" data-path="./">
|
||||
<li class="chapter " data-level="1.7" data-path="./">
|
||||
|
||||
<a href="./#rule-samples">
|
||||
|
||||
@ -388,7 +404,7 @@
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.6.1" data-path="./">
|
||||
<li class="chapter " data-level="1.7.1" data-path="./">
|
||||
|
||||
<a href="./#use-local-response">
|
||||
|
||||
@ -404,7 +420,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.2" data-path="./">
|
||||
<li class="chapter " data-level="1.7.2" data-path="./">
|
||||
|
||||
<a href="./#modify-request-header">
|
||||
|
||||
@ -420,7 +436,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.3" data-path="./">
|
||||
<li class="chapter " data-level="1.7.3" data-path="./">
|
||||
|
||||
<a href="./#modify-request-body">
|
||||
|
||||
@ -436,7 +452,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.4" data-path="./">
|
||||
<li class="chapter " data-level="1.7.4" data-path="./">
|
||||
|
||||
<a href="./#modify-the-request-target">
|
||||
|
||||
@ -452,7 +468,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.5" data-path="./">
|
||||
<li class="chapter " data-level="1.7.5" data-path="./">
|
||||
|
||||
<a href="./#modify-request-protocol">
|
||||
|
||||
@ -468,7 +484,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.6" data-path="./">
|
||||
<li class="chapter " data-level="1.7.6" data-path="./">
|
||||
|
||||
<a href="./#modify-response-status-code">
|
||||
|
||||
@ -484,7 +500,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.7" data-path="./">
|
||||
<li class="chapter " data-level="1.7.7" data-path="./">
|
||||
|
||||
<a href="./#modify-the-response-header">
|
||||
|
||||
@ -500,7 +516,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.6.8" data-path="./">
|
||||
<li class="chapter " data-level="1.7.8" data-path="./">
|
||||
|
||||
<a href="./#modify-response-data-and-delay">
|
||||
|
||||
@ -521,7 +537,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7" data-path="./">
|
||||
<li class="chapter " data-level="1.8" data-path="./">
|
||||
|
||||
<a href="./#config-certification">
|
||||
|
||||
@ -538,7 +554,7 @@
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.7.1" data-path="./">
|
||||
<li class="chapter " data-level="1.8.1" data-path="./">
|
||||
|
||||
<a href="./#config-root-ca-in-osx">
|
||||
|
||||
@ -554,7 +570,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.2" data-path="./">
|
||||
<li class="chapter " data-level="1.8.2" data-path="./">
|
||||
|
||||
<a href="./#config-root-ca-in-windows">
|
||||
|
||||
@ -570,7 +586,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.3" data-path="./">
|
||||
<li class="chapter " data-level="1.8.3" data-path="./">
|
||||
|
||||
<a href="./#config-osx-system-proxy">
|
||||
|
||||
@ -586,7 +602,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.4" data-path="./">
|
||||
<li class="chapter " data-level="1.8.4" data-path="./">
|
||||
|
||||
<a href="./#config-http-proxy-server">
|
||||
|
||||
@ -602,7 +618,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.5" data-path="./">
|
||||
<li class="chapter " data-level="1.8.5" data-path="./">
|
||||
|
||||
<a href="./#trust-root-ca-in-ios">
|
||||
|
||||
@ -618,7 +634,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.6" data-path="./">
|
||||
<li class="chapter " data-level="1.8.6" data-path="./">
|
||||
|
||||
<a href="./#trust-root-ca-in-ios-after-103">
|
||||
|
||||
@ -634,7 +650,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.7" data-path="./">
|
||||
<li class="chapter " data-level="1.8.7" data-path="./">
|
||||
|
||||
<a href="./#trust-root-ca-in-android">
|
||||
|
||||
@ -650,7 +666,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.7.8" data-path="./">
|
||||
<li class="chapter " data-level="1.8.8" data-path="./">
|
||||
|
||||
<a href="./#config-iosandroid-proxy-server">
|
||||
|
||||
@ -671,7 +687,7 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter active" data-level="1.8" data-path="./">
|
||||
<li class="chapter active" data-level="1.9" data-path="./">
|
||||
|
||||
<a href="./">
|
||||
|
||||
@ -893,7 +909,7 @@ AnyProxy.utils.systemProxyMgr.disableGlobalProxy();
|
||||
</li>
|
||||
<li>Sample</li>
|
||||
</ul>
|
||||
<pre><code class="lang-js"> <span class="hljs-keyword">const</span> AnyProxy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'AnyProxy'</span>);
|
||||
<pre><code class="lang-js"> <span class="hljs-keyword">const</span> AnyProxy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'anyproxy'</span>);
|
||||
<span class="hljs-keyword">const</span> exec = <span class="hljs-built_in">require</span>(<span class="hljs-string">'child_process'</span>).exec;
|
||||
|
||||
<span class="hljs-keyword">if</span> (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
|
||||
@ -932,6 +948,12 @@ anyproxy --intercept <span class="hljs-comment">#launch anyproxy and intercept a
|
||||
<ul>
|
||||
<li><a href="#config-certification">Appendix:how to trust CA</a></li>
|
||||
</ul>
|
||||
<h1 id="proxy-websocket">Proxy WebSocket</h1>
|
||||
<pre><code class="lang-bash">anyproxy --ws-intercept
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
<p>The <code>wss</code> requests will be handled automatically when the <code>HTTPS</code> intercept is turned on, but AnyProxy will not record the data by default. You need to specify the <code>--ws-intercept</code> to tell AnyProxy to record it.</p>
|
||||
</blockquote>
|
||||
<h1 id="rule-introduction">Rule Introduction</h1>
|
||||
<p>AnyProxy provides the ability to load your own rules written in javascript. With rule module, you could customize the logic to handle requests.</p>
|
||||
<blockquote>
|
||||
@ -1510,6 +1532,7 @@ newResponse.body += <span class="hljs-string">'--from anyproxy--'</spa
|
||||
<ul>
|
||||
<li>Besides installing root CA, you have to "turn on" the certificate for web manually in <em>settings - general - about - Certificate Trust Settings</em>. Otherwire, safari will not trust the root CA generated by AnyProxy.</li>
|
||||
</ul>
|
||||
<p><img src="https://zos.alipayobjects.com/rmsportal/hVWkXHrzHmOKOtCKGUWx.png" width="500"></p>
|
||||
<h3 id="trust-root-ca-in-android">trust root CA in Android</h3>
|
||||
<p>First of all, you need to download the root CA by clicking <em>Root CA</em> in web ui, and then scan the QR code.
|
||||
Installing CA in Android could be different based on the system, we list some common steps as below, but you can find the right way in you system with similar menu path.</p>
|
||||
@ -1521,7 +1544,7 @@ Installing CA in Android could be different based on the system, we list some co
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p><img src="https://zos.alipayobjects.com/rmsportal/hVWkXHrzHmOKOtCKGUWx.png" width="500"></p>
|
||||
<p>There are several file extensions of CA file which may not be compatible with all kinds of Android phones. <code>.crt</code> file is the most popular, while a few systems could only use .cer file such as OPPO R15. In AnyProxy, you can choose the type of certificate you need before installing.</p>
|
||||
<h3 id="config-iosandroid-proxy-server">config iOS/Android proxy server</h3>
|
||||
<ul>
|
||||
<li><p>proxy settings are placed in wifi setting</p>
|
||||
@ -1624,7 +1647,7 @@ You can change the request with rule of course. For this scenario, all you need
|
||||
<script>
|
||||
var gitbook = gitbook || [];
|
||||
gitbook.push(function() {
|
||||
gitbook.page.hasChanged({"page":{"title":"Introduction","level":"1.1","depth":1,"next":{"title":"Getting-Start","level":"1.2","depth":1,"anchor":"#getting-start","path":"README.md","ref":"README.md#getting-start","articles":[{"title":"Install","level":"1.2.1","depth":2,"anchor":"#install","path":"README.md","ref":"README.md#install","articles":[]},{"title":"Launch","level":"1.2.2","depth":2,"anchor":"#launch","path":"README.md","ref":"README.md#launch","articles":[]},{"title":"Options","level":"1.2.3","depth":2,"anchor":"#options","path":"README.md","ref":"README.md#options","articles":[]},{"title":"As Node Module","level":"1.2.4","depth":2,"anchor":"#use-anyproxy-as-an-npm-module","path":"README.md","ref":"README.md#use-anyproxy-as-an-npm-module","articles":[]}]},"dir":"ltr"},"config":{"plugins":[],"styles":{"website":"styles/website.css"},"pluginsConfig":{"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"AnyProxy","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"AnyProxy","language":"en","gitbook":"*","description":"A fully configurable http/https proxy in NodeJS"},"file":{"path":"README.md","mtime":"2018-02-05T02:16:34.020Z","type":"markdown"},"gitbook":{"version":"3.2.2","time":"2018-02-05T02:16:35.891Z"},"basePath":".","book":{"language":"en"}});
|
||||
gitbook.page.hasChanged({"page":{"title":"Introduction","level":"1.1","depth":1,"next":{"title":"Getting-Start","level":"1.2","depth":1,"anchor":"#getting-start","path":"README.md","ref":"README.md#getting-start","articles":[{"title":"Install","level":"1.2.1","depth":2,"anchor":"#install","path":"README.md","ref":"README.md#install","articles":[]},{"title":"Launch","level":"1.2.2","depth":2,"anchor":"#launch","path":"README.md","ref":"README.md#launch","articles":[]},{"title":"Options","level":"1.2.3","depth":2,"anchor":"#options","path":"README.md","ref":"README.md#options","articles":[]},{"title":"As Node Module","level":"1.2.4","depth":2,"anchor":"#use-anyproxy-as-an-npm-module","path":"README.md","ref":"README.md#use-anyproxy-as-an-npm-module","articles":[]}]},"dir":"ltr"},"config":{"plugins":[],"styles":{"website":"styles/website.css"},"pluginsConfig":{"livereload":{},"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"AnyProxy","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"AnyProxy","language":"en","gitbook":"*","description":"A fully configurable http/https proxy in NodeJS"},"file":{"path":"README.md","mtime":"2019-03-26T12:49:35.828Z","type":"markdown"},"gitbook":{"version":"3.2.2","time":"2019-03-26T12:49:36.852Z"},"basePath":".","book":{"language":"en"}});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
@ -1634,6 +1657,10 @@ You can change the request with rule of course. For this scenario, all you need
|
||||
<script src="../gitbook/theme.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-livereload/plugin.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -163,7 +163,7 @@ proxyServer.close();
|
||||
* Sample
|
||||
|
||||
```js
|
||||
const AnyProxy = require('AnyProxy');
|
||||
const AnyProxy = require('anyproxy');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
|
||||
@ -200,6 +200,13 @@ anyproxy --intercept #launch anyproxy and intercept all https traffic
|
||||
|
||||
* [Appendix:how to trust CA](#config-certification)
|
||||
|
||||
# Proxy WebSocket
|
||||
|
||||
```bash
|
||||
anyproxy --ws-intercept
|
||||
```
|
||||
> The `wss` requests will be handled automatically when the `HTTPS` intercept is turned on, but AnyProxy will not record the data by default. You need to specify the `--ws-intercept` to tell AnyProxy to record it.
|
||||
|
||||
# Rule Introduction
|
||||
|
||||
AnyProxy provides the ability to load your own rules written in javascript. With rule module, you could customize the logic to handle requests.
|
||||
@ -603,6 +610,8 @@ install :
|
||||
|
||||
* Besides installing root CA, you have to "turn on" the certificate for web manually in *settings - general - about - Certificate Trust Settings*. Otherwire, safari will not trust the root CA generated by AnyProxy.
|
||||
|
||||
<img src="https://zos.alipayobjects.com/rmsportal/hVWkXHrzHmOKOtCKGUWx.png" width="500" />
|
||||
|
||||
### trust root CA in Android
|
||||
First of all, you need to download the root CA by clicking *Root CA* in web ui, and then scan the QR code.
|
||||
Installing CA in Android could be different based on the system, we list some common steps as below, but you can find the right way in you system with similar menu path.
|
||||
@ -612,7 +621,7 @@ Installing CA in Android could be different based on the system, we list some co
|
||||
* Settings -> Security & Location > Encryption & credentials -> Install from storage, and find your CA file to install
|
||||
* Settings -> Security -> Install from SD card, and find you CA file to install
|
||||
|
||||
<img src="https://zos.alipayobjects.com/rmsportal/hVWkXHrzHmOKOtCKGUWx.png" width="500" />
|
||||
There are several file extensions of CA file which may not be compatible with all kinds of Android phones. `.crt` file is the most popular, while a few systems could only use .cer file such as OPPO R15. In AnyProxy, you can choose the type of certificate you need before installing.
|
||||
|
||||
### config iOS/Android proxy server
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
189
jest.config.js
Normal file
189
jest.config.js
Normal file
@ -0,0 +1,189 @@
|
||||
// For a detailed explanation regarding each configuration property, visit:
|
||||
// https://jestjs.io/docs/en/configuration.html
|
||||
|
||||
module.exports = {
|
||||
testTimeout: 10 * 1000,
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// Respect "browser" field in package.json when resolving modules
|
||||
// browser: false,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/private/var/folders/dn/t1cpcmtx6ng82d7qf8b02w7r0000gn/T/jest_dx",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: null,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
coverageDirectory: 'coverage',
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: null,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: null,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: null,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: null,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "json",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: null,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: null,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: null,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: null,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: 'node',
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: null,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jasmine2",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: null,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: null,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
@ -1,11 +1,16 @@
|
||||
'use strict'
|
||||
|
||||
const util = require('./util');
|
||||
const EasyCert = require('node-easy-cert');
|
||||
const co = require('co');
|
||||
const os = require('os');
|
||||
const inquirer = require('inquirer');
|
||||
|
||||
const util = require('./util');
|
||||
const logUtil = require('./log');
|
||||
|
||||
const options = {
|
||||
rootDirPath: util.getAnyProxyPath('certificates'),
|
||||
inMemory: false,
|
||||
defaultCertAttrs: [
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ name: 'organizationName', value: 'AnyProxy' },
|
||||
@ -54,4 +59,45 @@ crtMgr.getCAStatus = function *() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* trust the root ca by command
|
||||
*/
|
||||
crtMgr.trustRootCA = function *() {
|
||||
const platform = os.platform();
|
||||
const rootCAPath = crtMgr.getRootCAFilePath();
|
||||
const trustInquiry = [
|
||||
{
|
||||
type: 'list',
|
||||
name: 'trustCA',
|
||||
message: 'The rootCA is not trusted yet, install it to the trust store now?',
|
||||
choices: ['Yes', "No, I'll do it myself"]
|
||||
}
|
||||
];
|
||||
|
||||
if (platform === 'darwin') {
|
||||
const answer = yield inquirer.prompt(trustInquiry);
|
||||
if (answer.trustCA === 'Yes') {
|
||||
logUtil.info('About to trust the root CA, this may requires your password');
|
||||
// https://ss64.com/osx/security-cert.html
|
||||
const result = util.execScriptSync(`sudo security add-trusted-cert -d -k /Library/Keychains/System.keychain ${rootCAPath}`);
|
||||
if (result.status === 0) {
|
||||
logUtil.info('Root CA install, you are ready to intercept the https now');
|
||||
} else {
|
||||
console.error(result);
|
||||
logUtil.info('Failed to trust the root CA, please trust it manually');
|
||||
util.guideToHomePage();
|
||||
}
|
||||
} else {
|
||||
logUtil.info('Please trust the root CA manually so https interception works');
|
||||
util.guideToHomePage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (/^win/.test(process.platform)) {
|
||||
logUtil.info('You can install the root CA manually.');
|
||||
}
|
||||
logUtil.info('The root CA file path is: ' + crtMgr.getRootCAFilePath());
|
||||
}
|
||||
|
||||
module.exports = crtMgr;
|
||||
|
@ -4,18 +4,27 @@
|
||||
const async = require('async'),
|
||||
https = require('https'),
|
||||
tls = require('tls'),
|
||||
assert = require('assert'),
|
||||
crypto = require('crypto'),
|
||||
color = require('colorful'),
|
||||
certMgr = require('./certMgr'),
|
||||
logUtil = require('./log'),
|
||||
util = require('./util'),
|
||||
wsServerMgr = require('./wsServerMgr'),
|
||||
co = require('co'),
|
||||
constants = require('constants'),
|
||||
asyncTask = require('async-task-mgr');
|
||||
|
||||
/**
|
||||
* Create an https server
|
||||
*
|
||||
* @param {object} config
|
||||
* @param {number} config.port
|
||||
* @param {function} config.handler
|
||||
*/
|
||||
function createHttpsSNIServer(port, handler) {
|
||||
assert(port && handler, 'invalid param for https SNI server');
|
||||
|
||||
const createSecureContext = tls.createSecureContext || crypto.createSecureContext;
|
||||
//using sni to avoid multiple ports
|
||||
function SNIPrepareCert(serverName, SNICallback) {
|
||||
let keyContent,
|
||||
crtContent,
|
||||
@ -52,112 +61,67 @@ function SNIPrepareCert(serverName, SNICallback) {
|
||||
} else {
|
||||
logUtil.printLog('err occurred when prepare certs for SNI - ' + err, logUtil.T_ERR);
|
||||
logUtil.printLog('err occurred when prepare certs for SNI - ' + err.stack, logUtil.T_ERR);
|
||||
SNICallback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//config.port - port to start https server
|
||||
//config.handler - request handler
|
||||
|
||||
|
||||
/**
|
||||
* Create an https server
|
||||
*
|
||||
* @param {object} config
|
||||
* @param {number} config.port
|
||||
* @param {function} config.handler
|
||||
*/
|
||||
function createHttpsServer(config) {
|
||||
if (!config || !config.port || !config.handler) {
|
||||
throw (new Error('please assign a port'));
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
certMgr.getCertificate('anyproxy_internal_https_server', (err, keyContent, crtContent) => {
|
||||
const server = https.createServer({
|
||||
secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
|
||||
SNICallback: SNIPrepareCert,
|
||||
key: keyContent,
|
||||
cert: crtContent
|
||||
}, config.handler).listen(config.port);
|
||||
}, handler).listen(port);
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* create an https server that serving on IP address
|
||||
* @param @required {object} config
|
||||
* @param @required {string} config.ip the IP address of the server
|
||||
* @param @required {number} config.port the port to listen on
|
||||
* @param @required {function} handler the handler of each connect
|
||||
*/
|
||||
function createIPHttpsServer(config) {
|
||||
if (!config || !config.port || !config.handler) {
|
||||
throw (new Error('please assign a port'));
|
||||
}
|
||||
function createHttpsIPServer(ip, port, handler) {
|
||||
assert(ip && port && handler, 'invalid param for https IP server');
|
||||
|
||||
if (!config.ip) {
|
||||
throw (new Error('please assign an IP to create the https server'));
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
certMgr.getCertificate(config.ip, (err, keyContent, crtContent) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
certMgr.getCertificate(ip, (err, keyContent, crtContent) => {
|
||||
if (err) return reject(err);
|
||||
const server = https.createServer({
|
||||
secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
|
||||
key: keyContent,
|
||||
cert: crtContent
|
||||
}, config.handler).listen(config.port);
|
||||
cert: crtContent,
|
||||
}, handler).listen(port);
|
||||
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @class httpsServerMgr
|
||||
* @param {object} config
|
||||
* @param {function} config.handler handler to deal https request
|
||||
*
|
||||
*/
|
||||
class httpsServerMgr {
|
||||
constructor(config) {
|
||||
if (!config || !config.handler) {
|
||||
throw new Error('handler is required');
|
||||
}
|
||||
this.instanceDefaultHost = '127.0.0.1';
|
||||
this.httpsAsyncTask = new asyncTask();
|
||||
this.handler = config.handler;
|
||||
this.wsHandler = config.wsHandler
|
||||
this.asyncSNITaskName = `https_SNI_${Math.random()}`;
|
||||
this.activeServers = [];
|
||||
}
|
||||
|
||||
getSharedHttpsServer(hostname) {
|
||||
// ip address will have a unique name
|
||||
const finalHost = util.isIpDomain(hostname) ? hostname : this.instanceDefaultHost;
|
||||
|
||||
const self = this;
|
||||
function prepareServer(callback) {
|
||||
let instancePort;
|
||||
co(util.getFreePort)
|
||||
.then(co.wrap(function *(port) {
|
||||
instancePort = port;
|
||||
let httpsServer = null;
|
||||
const ifIPHost = hostname && util.isIp(hostname);
|
||||
const serverHost = '127.0.0.1';
|
||||
|
||||
// if ip address passed in, will create an IP http server
|
||||
if (util.isIpDomain(hostname)) {
|
||||
httpsServer = yield createIPHttpsServer({
|
||||
ip: hostname,
|
||||
port,
|
||||
handler: self.handler
|
||||
});
|
||||
function prepareServer(callback) {
|
||||
let port;
|
||||
Promise.resolve(util.getFreePort())
|
||||
.then(freePort => {
|
||||
port = freePort;
|
||||
if (ifIPHost) {
|
||||
return createHttpsIPServer(hostname, port, self.handler);
|
||||
} else {
|
||||
httpsServer = yield createHttpsServer({
|
||||
port,
|
||||
handler: self.handler
|
||||
});
|
||||
return createHttpsSNIServer(port, self.handler);
|
||||
}
|
||||
})
|
||||
.then(httpsServer => {
|
||||
self.activeServers.push(httpsServer);
|
||||
|
||||
wsServerMgr.getWsServer({
|
||||
server: httpsServer,
|
||||
@ -169,21 +133,19 @@ class httpsServerMgr {
|
||||
});
|
||||
|
||||
const result = {
|
||||
host: finalHost,
|
||||
port: instancePort,
|
||||
host: serverHost,
|
||||
port,
|
||||
};
|
||||
callback(null, result);
|
||||
return result;
|
||||
}))
|
||||
})
|
||||
.catch(e => {
|
||||
callback(e);
|
||||
});
|
||||
}
|
||||
|
||||
// same server for same host
|
||||
return new Promise((resolve, reject) => {
|
||||
// each ip address will gain a unit task name,
|
||||
// while the domain address will share a common task name
|
||||
self.httpsAsyncTask.addTask(`createHttpsServer-${finalHost}`, prepareServer, (error, serverInfo) => {
|
||||
self.httpsAsyncTask.addTask(ifIPHost ? hostname : serverHost, prepareServer, (error, serverInfo) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
@ -192,6 +154,12 @@ class httpsServerMgr {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.activeServers.forEach(server => {
|
||||
server.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = httpsServerMgr;
|
||||
|
@ -58,7 +58,7 @@ function printLog(content, type) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(color.magenta(`[AnyProxy WARN][${timeString}]: ` + content));
|
||||
console.error(color.yellow(`[AnyProxy WARN][${timeString}]: ` + content));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ module.exports.warn = (content) => {
|
||||
};
|
||||
|
||||
module.exports.error = (content) => {
|
||||
printLog(content, LogLevelMap.error);
|
||||
printLog(content, LogLevelMap.system_error);
|
||||
};
|
||||
|
||||
module.exports.ruleError = (content) => {
|
||||
|
@ -31,7 +31,7 @@ const WS_MESSAGE_FILE_PRFIX = 'ws_message_';
|
||||
const CACHE_DIR_PREFIX = 'cache_r';
|
||||
function getCacheDir() {
|
||||
const rand = Math.floor(Math.random() * 1000000),
|
||||
cachePath = path.join(proxyUtil.getAnyProxyPath('cache'), './' + CACHE_DIR_PREFIX + rand);
|
||||
cachePath = path.join(proxyUtil.getAnyProxyTmpPath(), './' + CACHE_DIR_PREFIX + rand);
|
||||
|
||||
fs.mkdirSync(cachePath);
|
||||
return cachePath;
|
||||
@ -86,11 +86,22 @@ class Recorder extends events.EventEmitter {
|
||||
this.globalId = 1;
|
||||
this.cachePath = getCacheDir();
|
||||
this.db = new Datastore();
|
||||
this.db.persistence.setAutocompactionInterval(5001);
|
||||
|
||||
this.recordBodyMap = []; // id - body
|
||||
}
|
||||
|
||||
setDbAutoCompact() {
|
||||
this.db.persistence.setAutocompactionInterval(5001);
|
||||
}
|
||||
|
||||
stopDbAutoCompact() {
|
||||
try {
|
||||
this.db.persistence.stopAutocompaction();
|
||||
} catch (e) {
|
||||
logUtil.printLog(e, logUtil.T_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
emitUpdate(id, info) {
|
||||
const self = this;
|
||||
if (info) {
|
||||
@ -126,12 +137,12 @@ class Recorder extends events.EventEmitter {
|
||||
*
|
||||
*/
|
||||
updateRecordWsMessage(id, message) {
|
||||
const cachePath = this.cachePath;
|
||||
if (id < 0) return;
|
||||
try {
|
||||
const recordWsMessageFile = path.join(cachePath, WS_MESSAGE_FILE_PRFIX + id);
|
||||
|
||||
this.getCacheFile(WS_MESSAGE_FILE_PRFIX + id, (err, recordWsMessageFile) => {
|
||||
if (err) return;
|
||||
fs.appendFile(recordWsMessageFile, wsMessageStingify(message) + ',', () => {});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logUtil.error(e.message + e.stack);
|
||||
@ -172,15 +183,16 @@ class Recorder extends events.EventEmitter {
|
||||
|
||||
updateRecordBody(id, info) {
|
||||
const self = this;
|
||||
const cachePath = self.cachePath;
|
||||
|
||||
if (id === -1) return;
|
||||
|
||||
if (!id || typeof info.resBody === 'undefined') return;
|
||||
//add to body map
|
||||
//ignore image data
|
||||
const bodyFile = path.join(cachePath, BODY_FILE_PRFIX + id);
|
||||
self.getCacheFile(BODY_FILE_PRFIX + id, (err, bodyFile) => {
|
||||
if (err) return;
|
||||
fs.writeFile(bodyFile, info.resBody, () => {});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,13 +201,16 @@ class Recorder extends events.EventEmitter {
|
||||
*/
|
||||
getBody(id, cb) {
|
||||
const self = this;
|
||||
const cachePath = self.cachePath;
|
||||
|
||||
if (id < 0) {
|
||||
cb && cb('');
|
||||
return;
|
||||
}
|
||||
self.getCacheFile(BODY_FILE_PRFIX + id, (error, bodyFile) => {
|
||||
if (error) {
|
||||
cb && cb(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const bodyFile = path.join(cachePath, BODY_FILE_PRFIX + id);
|
||||
fs.access(bodyFile, fs.F_OK || fs.R_OK, (err) => {
|
||||
if (err) {
|
||||
cb && cb(err);
|
||||
@ -203,6 +218,7 @@ class Recorder extends events.EventEmitter {
|
||||
fs.readFile(bodyFile, cb);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getDecodedBody(id, cb) {
|
||||
@ -242,18 +258,16 @@ class Recorder extends events.EventEmitter {
|
||||
bodyContent = iconv.decode(bodyContent, currentCharset);
|
||||
}
|
||||
|
||||
result.mime = contentType;
|
||||
result.content = bodyContent.toString();
|
||||
result.type = contentType && /application\/json/i.test(contentType) ? 'json' : 'text';
|
||||
} else if (contentType && /image/i.test(contentType)) {
|
||||
result.type = 'image';
|
||||
result.mime = contentType;
|
||||
result.content = bodyContent;
|
||||
} else {
|
||||
result.type = contentType;
|
||||
result.mime = contentType;
|
||||
result.content = bodyContent.toString();
|
||||
}
|
||||
result.mime = contentType;
|
||||
result.fileName = path.basename(record.path);
|
||||
result.statusCode = record.statusCode;
|
||||
} catch (e) {
|
||||
@ -270,14 +284,16 @@ class Recorder extends events.EventEmitter {
|
||||
*
|
||||
*/
|
||||
getDecodedWsMessage(id, cb) {
|
||||
const self = this;
|
||||
const cachePath = self.cachePath;
|
||||
|
||||
if (id < 0) {
|
||||
cb && cb([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const wsMessageFile = path.join(cachePath, WS_MESSAGE_FILE_PRFIX + id);
|
||||
this.getCacheFile(WS_MESSAGE_FILE_PRFIX + id, (outError, wsMessageFile) => {
|
||||
if (outError) {
|
||||
cb && cb(outError);
|
||||
return;
|
||||
}
|
||||
fs.access(wsMessageFile, fs.F_OK || fs.R_OK, (err) => {
|
||||
if (err) {
|
||||
cb && cb(err);
|
||||
@ -301,6 +317,7 @@ class Recorder extends events.EventEmitter {
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getSingleRecord(id, cb) {
|
||||
@ -327,9 +344,23 @@ class Recorder extends events.EventEmitter {
|
||||
}
|
||||
|
||||
clear() {
|
||||
logUtil.printLog('clearing cache file...');
|
||||
const self = this;
|
||||
proxyUtil.deleteFolderContentsRecursive(self.cachePath, true);
|
||||
}
|
||||
|
||||
getCacheFile(fileName, cb) {
|
||||
const self = this;
|
||||
const cachePath = self.cachePath;
|
||||
const filepath = path.join(cachePath, fileName);
|
||||
|
||||
if (filepath.indexOf(cachePath) !== 0) {
|
||||
cb && cb(new Error('invalid cache file path'));
|
||||
} else {
|
||||
cb && cb(null, filepath);
|
||||
return filepath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Recorder;
|
||||
|
@ -44,7 +44,7 @@ const getErrorResponse = (error, fullUrl) => {
|
||||
header: {
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Proxy-Error': true,
|
||||
'Proxy-Error-Message': error || 'null'
|
||||
'Proxy-Error-Message': error ? JSON.stringify(error) : 'null'
|
||||
},
|
||||
body: requestErrorHandler.getErrorContent(error, fullUrl)
|
||||
};
|
||||
@ -120,7 +120,7 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
|
||||
// only do unzip when there is res data
|
||||
if (ifServerGzipped && originContentLen) {
|
||||
refactContentEncoding();
|
||||
zlib.gunzip(serverResData, (err, buff) => { // TODO test case to cover
|
||||
zlib.gunzip(serverResData, (err, buff) => {
|
||||
if (err) {
|
||||
rejectParsing(err);
|
||||
} else {
|
||||
@ -129,7 +129,7 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
|
||||
});
|
||||
} else if (isServerDeflated && originContentLen) {
|
||||
refactContentEncoding();
|
||||
zlib.inflateRaw(serverResData, (err, buff) => { // TODO test case to cover
|
||||
zlib.inflate(serverResData, (err, buff) => {
|
||||
if (err) {
|
||||
rejectParsing(err);
|
||||
} else {
|
||||
@ -212,18 +212,39 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
|
||||
@param @required wsClient the ws client of WebSocket
|
||||
*
|
||||
*/
|
||||
function getWsReqInfo(wsClient) {
|
||||
const upgradeReq = wsClient.upgradeReq || {};
|
||||
const header = upgradeReq.headers || {};
|
||||
const host = header.host;
|
||||
function getWsReqInfo(wsReq) {
|
||||
const headers = wsReq.headers || {};
|
||||
const host = headers.host;
|
||||
const hostName = host.split(':')[0];
|
||||
const port = host.split(':')[1];
|
||||
|
||||
// TODO 如果是windows机器,url是不是全路径?需要对其过滤,取出
|
||||
const path = upgradeReq.url || '/';
|
||||
const path = wsReq.url || '/';
|
||||
|
||||
const isEncript = wsReq.connection && wsReq.connection.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 originHeaders = Object.assign({}, headers);
|
||||
const originHeaderKeys = Object.keys(originHeaders);
|
||||
originHeaderKeys.forEach((key) => {
|
||||
// if the key matchs 'sec-websocket', delete it
|
||||
if (/sec-websocket/ig.test(key)) {
|
||||
delete originHeaders[key];
|
||||
}
|
||||
});
|
||||
|
||||
delete originHeaders.connection;
|
||||
delete originHeaders.upgrade;
|
||||
return originHeaders;
|
||||
}
|
||||
|
||||
|
||||
const isEncript = true && upgradeReq.connection && upgradeReq.connection.encrypted;
|
||||
return {
|
||||
headers: headers, // the full headers of origin ws connection
|
||||
noWsHeaders: getNoWsHeaders(),
|
||||
hostName: hostName,
|
||||
port: port,
|
||||
path: path,
|
||||
@ -251,7 +272,15 @@ function getUserReqHandler(userRule, recorder) {
|
||||
|
||||
const host = req.headers.host;
|
||||
const protocol = (!!req.connection.encrypted && !(/^http:/).test(req.url)) ? 'https' : 'http';
|
||||
const fullUrl = protocol === 'http' ? req.url : (protocol + '://' + host + req.url);
|
||||
|
||||
// try find fullurl https://github.com/alibaba/anyproxy/issues/419
|
||||
let fullUrl = protocol + '://' + host + req.url;
|
||||
if (protocol === 'http') {
|
||||
const reqUrlPattern = url.parse(req.url);
|
||||
if (reqUrlPattern.host && reqUrlPattern.protocol) {
|
||||
fullUrl = req.url;
|
||||
}
|
||||
}
|
||||
|
||||
const urlPattern = url.parse(fullUrl);
|
||||
const path = urlPattern.path;
|
||||
@ -529,14 +558,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);
|
||||
@ -559,11 +588,19 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
cltSocket.on('error', (error) => {
|
||||
logUtil.printLog(util.collectErrorLog(error), logUtil.T_ERR);
|
||||
co.wrap(function *() {
|
||||
try {
|
||||
yield userRule.onClientSocketError(requestDetail, error);
|
||||
} catch (e) { }
|
||||
});
|
||||
});
|
||||
cltSocket.on('end', () => {
|
||||
requestStream.push(null);
|
||||
});
|
||||
});
|
||||
})
|
||||
)
|
||||
.then((result) => {
|
||||
// log and recorder
|
||||
if (shouldIntercept) {
|
||||
@ -664,7 +701,7 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
|
||||
* get a websocket event handler
|
||||
@param @required {object} wsClient
|
||||
*/
|
||||
function getWsHandler(userRule, recorder, wsClient) {
|
||||
function getWsHandler(userRule, recorder, wsClient, wsReq) {
|
||||
const self = this;
|
||||
try {
|
||||
let resourceInfoId = -1;
|
||||
@ -672,10 +709,12 @@ function getWsHandler(userRule, recorder, wsClient) {
|
||||
wsMessages: [] // all ws messages go through AnyProxy
|
||||
};
|
||||
const clientMsgQueue = [];
|
||||
const serverInfo = getWsReqInfo(wsClient);
|
||||
const wsUrl = `${serverInfo.protocol}://${serverInfo.hostName}:${serverInfo.port}${serverInfo.path}`;
|
||||
const serverInfo = getWsReqInfo(wsReq);
|
||||
const serverInfoPort = serverInfo.port ? `:${serverInfo.port}` : '';
|
||||
const wsUrl = `${serverInfo.protocol}://${serverInfo.hostName}${serverInfoPort}${serverInfo.path}`;
|
||||
const proxyWs = new WebSocket(wsUrl, '', {
|
||||
rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized
|
||||
rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized,
|
||||
headers: serverInfo.noWsHeaders
|
||||
});
|
||||
|
||||
if (recorder) {
|
||||
@ -684,7 +723,7 @@ function getWsHandler(userRule, recorder, wsClient) {
|
||||
method: 'WebSocket',
|
||||
path: serverInfo.path,
|
||||
url: wsUrl,
|
||||
req: wsClient.upgradeReq || {},
|
||||
req: wsReq,
|
||||
startTime: new Date().getTime()
|
||||
});
|
||||
resourceInfoId = recorder.appendRecord(resourceInfo);
|
||||
@ -763,8 +802,9 @@ function getWsHandler(userRule, recorder, wsClient) {
|
||||
}
|
||||
|
||||
// this event is fired when the connection is build and headers is returned
|
||||
proxyWs.on('headers', (headers, response) => {
|
||||
proxyWs.on('upgrade', (response) => {
|
||||
resourceInfo.endTime = new Date().getTime();
|
||||
const headers = response.headers;
|
||||
resourceInfo.res = { //construct a self-defined res object
|
||||
statusCode: response.statusCode,
|
||||
headers: headers,
|
||||
@ -813,7 +853,6 @@ function getWsHandler(userRule, recorder, wsClient) {
|
||||
}
|
||||
|
||||
class RequestHandler {
|
||||
|
||||
/**
|
||||
* Creates an instance of RequestHandler.
|
||||
*
|
||||
@ -854,7 +893,8 @@ class RequestHandler {
|
||||
|
||||
reqHandlerCtx.httpsServerMgr = new HttpsServerMgr({
|
||||
handler: reqHandlerCtx.userRequestHandler,
|
||||
wsHandler: reqHandlerCtx.wsHandler // websocket
|
||||
wsHandler: reqHandlerCtx.wsHandler, // websocket
|
||||
hostname: '127.0.0.1',
|
||||
});
|
||||
|
||||
this.connectReqHandler = getConnectReqHandler.apply(reqHandlerCtx, [userRule, recorder, reqHandlerCtx.httpsServerMgr]);
|
||||
|
@ -5,7 +5,7 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const request = require('request');
|
||||
|
||||
const cachePath = proxyUtil.getAnyProxyPath('cache');
|
||||
const cachePath = proxyUtil.getAnyProxyTmpPath();
|
||||
|
||||
/**
|
||||
* download a file and cache
|
||||
|
@ -66,4 +66,16 @@ module.exports = {
|
||||
*onConnectError(requestDetail, error) {
|
||||
return null;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {any} requestDetail
|
||||
* @param {any} error
|
||||
* @returns
|
||||
*/
|
||||
*onClientSocketError(requestDetail, error) {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
47
lib/util.js
47
lib/util.js
@ -4,10 +4,12 @@ const fs = require('fs'),
|
||||
path = require('path'),
|
||||
mime = require('mime-types'),
|
||||
color = require('colorful'),
|
||||
crypto = require('crypto'),
|
||||
child_process = require('child_process'),
|
||||
os = require('os'),
|
||||
Buffer = require('buffer').Buffer,
|
||||
logUtil = require('./log');
|
||||
const networkInterfaces = require('os').networkInterfaces();
|
||||
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
|
||||
// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
|
||||
module.exports.lower_keys = (obj) => {
|
||||
@ -52,6 +54,14 @@ module.exports.getAnyProxyPath = function (pathName) {
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
module.exports.getAnyProxyTmpPath = function () {
|
||||
const targetPath = path.join(os.tmpdir(), 'anyproxy', 'cache');
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
fs.mkdirSync(targetPath, { recursive: true });
|
||||
}
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
module.exports.simpleRender = function (str, object, regexp) {
|
||||
return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
|
||||
if (match.charAt(0) === '\\') {
|
||||
@ -298,7 +308,7 @@ module.exports.getByteSize = function (content) {
|
||||
/*
|
||||
* identify whether the
|
||||
*/
|
||||
module.exports.isIpDomain = function (domain) {
|
||||
module.exports.isIp = function (domain) {
|
||||
if (!domain) {
|
||||
return false;
|
||||
}
|
||||
@ -307,17 +317,22 @@ module.exports.isIpDomain = function (domain) {
|
||||
return ipReg.test(domain);
|
||||
};
|
||||
|
||||
/**
|
||||
* To generic a Sec-WebSocket-Accept value
|
||||
* 1. append the `Sec-WebSocket-Key` request header with `matic string`
|
||||
* 2. get sha1 hash of the string
|
||||
* 3. get base64 of the sha1 hash
|
||||
*/
|
||||
module.exports.genericWsSecAccept = function (wsSecKey) {
|
||||
// the string to generate the Sec-WebSocket-Accept
|
||||
const magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
const targetString = `${wsSecKey}${magicString}`;
|
||||
const shasum = crypto.createHash('sha1');
|
||||
shasum.update(targetString);
|
||||
return shasum.digest('base64');
|
||||
module.exports.execScriptSync = function (cmd) {
|
||||
let stdout,
|
||||
status = 0;
|
||||
try {
|
||||
stdout = child_process.execSync(cmd);
|
||||
} catch (err) {
|
||||
stdout = err.stdout;
|
||||
status = err.status;
|
||||
}
|
||||
|
||||
return {
|
||||
stdout: stdout.toString(),
|
||||
status
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.guideToHomePage = function () {
|
||||
logUtil.info('Refer to http://anyproxy.io for more detail');
|
||||
};
|
||||
|
@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const DEFAULT_WEB_PORT = 8002; // port for web interface
|
||||
|
||||
const express = require('express'),
|
||||
url = require('url'),
|
||||
bodyParser = require('body-parser'),
|
||||
@ -14,11 +12,16 @@ const express = require('express'),
|
||||
wsServer = require('./wsServer'),
|
||||
juicer = require('juicer'),
|
||||
ip = require('ip'),
|
||||
compress = require('compression');
|
||||
compress = require('compression'),
|
||||
pug = require('pug');
|
||||
|
||||
const DEFAULT_WEB_PORT = 8002; // port for web interface
|
||||
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
const MAX_CONTENT_SIZE = 1024 * 2000; // 2000kb
|
||||
|
||||
const certFileTypes = ['crt', 'cer', 'pem', 'der'];
|
||||
/**
|
||||
*
|
||||
*
|
||||
@ -26,7 +29,6 @@ const MAX_CONTENT_SIZE = 1024 * 2000; // 2000kb
|
||||
* @extends {events.EventEmitter}
|
||||
*/
|
||||
class webInterface extends events.EventEmitter {
|
||||
|
||||
/**
|
||||
* Creates an instance of webInterface.
|
||||
*
|
||||
@ -68,10 +70,9 @@ class webInterface extends events.EventEmitter {
|
||||
customMenu = ''; // userRule._getCustomMenu();
|
||||
} catch (e) { }
|
||||
|
||||
const myAbsAddress = 'http://' + ipAddress + ':' + self.webPort + '/',
|
||||
staticDir = path.join(__dirname, '../', webBasePath);
|
||||
|
||||
const staticDir = path.join(__dirname, '../', webBasePath);
|
||||
const app = express();
|
||||
|
||||
app.use(compress()); //invoke gzip
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('note', 'THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE');
|
||||
@ -147,10 +148,8 @@ class webInterface extends events.EventEmitter {
|
||||
if (err || !result) {
|
||||
res.json({});
|
||||
} else if (result.statusCode === 200 && result.mime) {
|
||||
if (result.type === 'json' ||
|
||||
result.mime.indexOf('text') === 0 ||
|
||||
// deal with 'application/x-javascript' and 'application/javascript'
|
||||
result.mime.indexOf('javascript') > -1) {
|
||||
if (/json|text|javascript/.test(result.mime)) {
|
||||
_resContent();
|
||||
} else if (result.type === 'image') {
|
||||
_resDownload(false);
|
||||
@ -162,7 +161,7 @@ class webInterface extends events.EventEmitter {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.end({});
|
||||
res.end('');
|
||||
}
|
||||
});
|
||||
|
||||
@ -201,12 +200,18 @@ class webInterface extends events.EventEmitter {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/downloadCrt', (req, res) => {
|
||||
const pageFn = pug.compileFile(path.join(__dirname, '../resource/cert_download.pug'));
|
||||
res.end(pageFn({ ua: req.get('user-agent') }));
|
||||
});
|
||||
|
||||
app.get('/fetchCrtFile', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const _crtFilePath = certMgr.getRootCAFilePath();
|
||||
if (_crtFilePath) {
|
||||
const fileType = certFileTypes.indexOf(req.query.type) !== -1 ? req.query.type : 'crt';
|
||||
res.setHeader('Content-Type', 'application/x-x509-ca-cert');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="rootCA.crt"');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="rootCA.${fileType}"`);
|
||||
res.end(fs.readFileSync(_crtFilePath, { encoding: null }));
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
@ -214,38 +219,21 @@ class webInterface extends events.EventEmitter {
|
||||
}
|
||||
});
|
||||
|
||||
//make qr code
|
||||
app.get('/qr', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const qr = qrCode.qrcode(4, 'M'),
|
||||
targetUrl = myAbsAddress;
|
||||
qr.addData(targetUrl);
|
||||
qr.make();
|
||||
const qrImageTag = qr.createImgTag(4);
|
||||
const resDom = '<a href="__url"> __img <br> click or scan qr code to start client </a>'.replace(/__url/, targetUrl).replace(/__img/, qrImageTag);
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(resDom);
|
||||
});
|
||||
|
||||
app.get('/api/getQrCode', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const qr = qrCode.qrcode(4, 'M'),
|
||||
targetUrl = myAbsAddress + 'fetchCrtFile';
|
||||
|
||||
const qr = qrCode.qrcode(4, 'M');
|
||||
const targetUrl = req.protocol + '://' + req.get('host') + '/downloadCrt';
|
||||
const isRootCAFileExists = certMgr.isRootCAFileExists();
|
||||
|
||||
qr.addData(targetUrl);
|
||||
qr.make();
|
||||
const qrImageTag = qr.createImgTag(4);
|
||||
|
||||
// resDom = '<a href="__url"> __img <br> click or scan qr code to download rootCA.crt </a>'.replace(/__url/,targetUrl).replace(/__img/,qrImageTag);
|
||||
// res.setHeader("Content-Type", "text/html");
|
||||
// res.end(resDom);
|
||||
|
||||
const isRootCAFileExists = certMgr.isRootCAFileExists();
|
||||
res.json({
|
||||
status: 'success',
|
||||
url: targetUrl,
|
||||
isRootCAFileExists,
|
||||
qrImgDom: qrImageTag
|
||||
qrImgDom: qr.createImgTag(4)
|
||||
});
|
||||
});
|
||||
|
||||
@ -319,11 +307,15 @@ class webInterface extends events.EventEmitter {
|
||||
}
|
||||
|
||||
close() {
|
||||
this.server && this.server.close();
|
||||
this.wsServer && this.wsServer.closeAll();
|
||||
this.server = null;
|
||||
this.wsServer = null;
|
||||
this.proxyInstance = null;
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
self.server && self.server.close();
|
||||
self.wsServer && self.wsServer.closeAll();
|
||||
self.server = null;
|
||||
self.wsServer = null;
|
||||
self.proxyInstance = null;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,7 @@ class wsServer {
|
||||
const self = this;
|
||||
self.config = config;
|
||||
self.recorder = recorder;
|
||||
self.checkBroadcastFlagTimer = null;
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -78,7 +79,7 @@ class wsServer {
|
||||
// the flat to indicate wheter to broadcast the record
|
||||
let broadcastFlag = true;
|
||||
|
||||
setInterval(() => {
|
||||
self.checkBroadcastFlagTimer = setInterval(() => {
|
||||
broadcastFlag = true;
|
||||
sendMultipleMessage();
|
||||
}, 50);
|
||||
@ -102,7 +103,7 @@ class wsServer {
|
||||
try {
|
||||
data = JSON.stringify(data);
|
||||
} catch (e) {
|
||||
console.error('==> errorr when do broadcast ', e, data);
|
||||
console.error('==> error when do broadcast ', e, data);
|
||||
}
|
||||
}
|
||||
for (const client of wss.clients) {
|
||||
@ -161,6 +162,9 @@ class wsServer {
|
||||
|
||||
closeAll() {
|
||||
const self = this;
|
||||
if (self.checkBroadcastFlagTimer) {
|
||||
clearInterval(self.checkBroadcastFlagTimer);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
self.wss.close((e) => {
|
||||
if (e) {
|
||||
|
44
package.json
44
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "anyproxy",
|
||||
"version": "4.0.2",
|
||||
"version": "4.1.3",
|
||||
"description": "A fully configurable HTTP/HTTPS proxy in Node.js",
|
||||
"main": "proxy.js",
|
||||
"bin": {
|
||||
@ -23,28 +23,30 @@
|
||||
"express": "^4.8.5",
|
||||
"fast-json-stringify": "^0.17.0",
|
||||
"iconv-lite": "^0.4.6",
|
||||
"inquirer": "^3.0.1",
|
||||
"inquirer": "^5.2.0",
|
||||
"ip": "^0.3.2",
|
||||
"juicer": "^0.6.6-stable",
|
||||
"mime-types": "2.1.11",
|
||||
"moment": "^2.15.1",
|
||||
"nedb": "^1.8.0",
|
||||
"node-easy-cert": "^1.0.0",
|
||||
"node-forge": "^0.6.39",
|
||||
"npm": "^2.7.0",
|
||||
"pug": "^2.0.0-beta6",
|
||||
"q": "^1.4.1",
|
||||
"qrcode-npm": "0.0.3",
|
||||
"request": "^2.74.0",
|
||||
"stream-throttle": "^0.1.3",
|
||||
"svg-inline-react": "^1.0.2",
|
||||
"thunkify": "^2.1.2",
|
||||
"whatwg-fetch": "^1.0.0",
|
||||
"ws": "^2.2.0"
|
||||
"ws": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.3",
|
||||
"antd": "^2.5.0",
|
||||
"autoprefixer": "^6.4.1",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-import": "^1.0.0",
|
||||
"babel-plugin-transform-runtime": "^6.15.0",
|
||||
@ -55,23 +57,16 @@
|
||||
"babel-register": "^6.11.6",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"css-loader": "^0.23.1",
|
||||
"eslint": "^3.5.0",
|
||||
"eslint": ">=4.18.2",
|
||||
"eslint-config-airbnb": "^15.1.0",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-jsx-a11y": "^5.1.1",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^0.9.0",
|
||||
"https-proxy-agent": "^1.0.0",
|
||||
"jasmine": "^2.5.3",
|
||||
"koa": "^1.2.1",
|
||||
"koa-body": "^1.4.0",
|
||||
"koa-router": "^5.4.0",
|
||||
"koa-send": "^3.2.0",
|
||||
"koa-websocket": "^2.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"less": "^2.7.1",
|
||||
"less-loader": "^2.2.3",
|
||||
"memwatch-next": "^0.3.0",
|
||||
"node-simhash": "^0.1.0",
|
||||
"nodeunit": "^0.9.1",
|
||||
"phantom": "^4.0.0",
|
||||
@ -88,19 +83,19 @@
|
||||
"stream-equal": "0.1.8",
|
||||
"style-loader": "^0.13.1",
|
||||
"svg-inline-loader": "^0.7.1",
|
||||
"svg-inline-react": "^1.0.2",
|
||||
"tunnel": "^0.0.6",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.12.9",
|
||||
"urllib": "^2.34.2",
|
||||
"webpack": "^3.10.0",
|
||||
"worker-loader": "^0.7.1"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "npm run buildweb",
|
||||
"test": "node test.js",
|
||||
"test": "npx jest",
|
||||
"lint": "eslint .",
|
||||
"testserver": "node test/server/startServer.js",
|
||||
"testOutWeb": "jasmine test/spec_outweb/test_realweb_spec.js",
|
||||
"buildweb": "NODE_ENV=production webpack --config web/webpack.config.js --process --colors",
|
||||
"webserver": "NODE_ENV=test webpack --config web/webpack.config.js --process --colors --watch",
|
||||
"buildweb": "NODE_ENV=production webpack --config web/webpack.config.js --colors",
|
||||
"webserver": "NODE_ENV=test webpack --config web/webpack.config.js --colors --watch",
|
||||
"doc:serve": "node build_scripts/prebuild-doc.js && gitbook serve ./docs-src ./docs --log debug",
|
||||
"doc:build": "./build_scripts/build-doc-site.sh"
|
||||
},
|
||||
@ -112,5 +107,8 @@
|
||||
"url": "https://github.com/alibaba/anyproxy"
|
||||
},
|
||||
"author": "ottomao@gmail.com",
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
81
proxy.js
81
proxy.js
@ -14,25 +14,6 @@ const http = require('http'),
|
||||
wsServerMgr = require('./lib/wsServerMgr'),
|
||||
ThrottleGroup = require('stream-throttle').ThrottleGroup;
|
||||
|
||||
// const memwatch = require('memwatch-next');
|
||||
|
||||
// setInterval(() => {
|
||||
// console.log(process.memoryUsage());
|
||||
// const rss = Math.ceil(process.memoryUsage().rss / 1000 / 1000);
|
||||
// console.log('Program is using ' + rss + ' mb of Heap.');
|
||||
// }, 1000);
|
||||
|
||||
// memwatch.on('stats', (info) => {
|
||||
// console.log('gc !!');
|
||||
// console.log(process.memoryUsage());
|
||||
// const rss = Math.ceil(process.memoryUsage().rss / 1000 / 1000);
|
||||
// console.log('GC !! Program is using ' + rss + ' mb of Heap.');
|
||||
|
||||
// // var heapUsed = Math.ceil(process.memoryUsage().heapUsed / 1000);
|
||||
// // console.log("Program is using " + heapUsed + " kb of Heap.");
|
||||
// // console.log(info);
|
||||
// });
|
||||
|
||||
const T_TYPE_HTTP = 'http',
|
||||
T_TYPE_HTTPS = 'https',
|
||||
DEFAULT_TYPE = T_TYPE_HTTP;
|
||||
@ -47,7 +28,6 @@ const PROXY_STATUS_CLOSED = 'CLOSED';
|
||||
* @extends {events.EventEmitter}
|
||||
*/
|
||||
class ProxyCore extends events.EventEmitter {
|
||||
|
||||
/**
|
||||
* Creates an instance of ProxyCore.
|
||||
*
|
||||
@ -248,7 +228,6 @@ class ProxyCore extends events.EventEmitter {
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* close the proxy server
|
||||
*
|
||||
@ -271,10 +250,14 @@ class ProxyCore extends events.EventEmitter {
|
||||
for (const cltSocketItem of this.requestHandler.cltSockets) {
|
||||
const key = cltSocketItem[0];
|
||||
const cltSocket = cltSocketItem[1];
|
||||
logUtil.printLog(`endding https cltSocket : ${key}`);
|
||||
logUtil.printLog(`closing https cltSocket : ${key}`);
|
||||
cltSocket.end();
|
||||
}
|
||||
|
||||
if (this.requestHandler.httpsServerMgr) {
|
||||
this.requestHandler.httpsServerMgr.close();
|
||||
}
|
||||
|
||||
if (this.socketPool) {
|
||||
for (const key in this.socketPool) {
|
||||
this.socketPool[key].destroy();
|
||||
@ -326,51 +309,45 @@ class ProxyServer extends ProxyCore {
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.recorder) {
|
||||
this.recorder.setDbAutoCompact();
|
||||
}
|
||||
|
||||
// start web interface if neeeded
|
||||
if (this.proxyWebinterfaceConfig && this.proxyWebinterfaceConfig.enable) {
|
||||
this.webServerInstance = new WebInterface(this.proxyWebinterfaceConfig, this.recorder);
|
||||
}
|
||||
|
||||
// start web server
|
||||
this.webServerInstance.start().then(() => {
|
||||
this.webServerInstance.start()
|
||||
// start proxy core
|
||||
.then(() => {
|
||||
super.start();
|
||||
})
|
||||
.catch((e) => {
|
||||
this.emit('error', e);
|
||||
});
|
||||
} else {
|
||||
super.start();
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
return new Promise((resolve, reject) => {
|
||||
super.close()
|
||||
.then((error) => {
|
||||
if (error) {
|
||||
resolve(error);
|
||||
const self = this;
|
||||
// release recorder
|
||||
if (self.recorder) {
|
||||
self.recorder.stopDbAutoCompact();
|
||||
self.recorder.clear();
|
||||
}
|
||||
});
|
||||
self.recorder = null;
|
||||
|
||||
if (this.recorder) {
|
||||
logUtil.printLog('clearing cache file...');
|
||||
this.recorder.clear();
|
||||
}
|
||||
const tmpWebServer = this.webServerInstance;
|
||||
this.recorder = null;
|
||||
this.webServerInstance = null;
|
||||
if (tmpWebServer) {
|
||||
logUtil.printLog('closing webserver...');
|
||||
tmpWebServer.close((error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
logUtil.printLog(`proxy web server close FAILED: ${error.message}`, logUtil.T_ERR);
|
||||
} else {
|
||||
logUtil.printLog(`proxy web server closed at ${this.proxyHostName} : ${this.webPort}`);
|
||||
}
|
||||
|
||||
resolve(error);
|
||||
})
|
||||
} else {
|
||||
resolve(null);
|
||||
// close ProxyCore
|
||||
return super.close()
|
||||
// release webInterface
|
||||
.then(() => {
|
||||
if (self.webServerInstance) {
|
||||
const tmpWebServer = self.webServerInstance;
|
||||
self.webServerInstance = null;
|
||||
logUtil.printLog('closing webInterface...');
|
||||
return tmpWebServer.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
91
resource/cert_download.pug
Normal file
91
resource/cert_download.pug
Normal file
@ -0,0 +1,91 @@
|
||||
doctype html
|
||||
html(lang="en")
|
||||
head
|
||||
title Download rootCA
|
||||
meta(name='viewport', content='initial-scale=1, maximum-scale=0.5, minimum-scale=1, user-scalable=no')
|
||||
style.
|
||||
body {
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
|
||||
}
|
||||
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 36px;
|
||||
margin-bottom: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.any {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.proxy {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
margin: 20px 0 6px;
|
||||
}
|
||||
|
||||
.button {
|
||||
text-align: center;
|
||||
padding: 4px 15px 5px 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
border-color: #108ee9;
|
||||
color: rgba(0, 0, 0, .65);
|
||||
background-color: #fff;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: #fff;
|
||||
background-color: #108ee9;
|
||||
border-color: #108ee9;
|
||||
}
|
||||
|
||||
.more {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
word-break: break-all;
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
body
|
||||
.logo
|
||||
span.any Any
|
||||
span.proxy Proxy
|
||||
.title Download:
|
||||
.content Select a CA file to download, the .crt file is commonly used.
|
||||
a(href="/fetchCrtFile?type=crt").button.primary rootCA.crt
|
||||
a(href="/fetchCrtFile?type=cer").button rootCA.cer
|
||||
.more More
|
||||
.buttons(style='display: none')
|
||||
a(href="/fetchCrtFile?type=pem").button rootCA.pem
|
||||
a(href="/fetchCrtFile?type=der").button rootCA.der
|
||||
.title User-Agent:
|
||||
.content #{ua}
|
||||
script(type='text/javascript').
|
||||
window.document.querySelector('.more').addEventListener('click', function (e) {
|
||||
e.target.style.display = 'none';
|
||||
window.document.querySelector('.buttons').style.display = 'block';
|
||||
});
|
16
test.js
16
test.js
@ -1,16 +0,0 @@
|
||||
const Jasmine = require('jasmine');
|
||||
|
||||
const jasmine = new Jasmine();
|
||||
const util = require('./lib/util');
|
||||
const path = require('path');
|
||||
|
||||
const testTmpPath = path.join(__dirname, './test/temp');
|
||||
const configFilePath = path.join(__dirname, './test/jasmine.json');
|
||||
// rm - rf./test / temp /
|
||||
util.deleteFolderContentsRecursive(testTmpPath);
|
||||
|
||||
jasmine.loadConfigFile(configFilePath);
|
||||
jasmine.configureDefaultReporter({
|
||||
showColors: false
|
||||
});
|
||||
jasmine.execute();
|
69
test/__snapshots__/basic.spec.js.snap
Normal file
69
test/__snapshots__/basic.spec.js.snap
Normal file
@ -0,0 +1,69 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`http - HTTP verbs DELETE: args 1`] = `
|
||||
Object {
|
||||
"foo": "bar",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`http - HTTP verbs DELETE: data 1`] = `""`;
|
||||
|
||||
exports[`http - HTTP verbs GET: args 1`] = `
|
||||
Object {
|
||||
"param": "param_value",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`http - HTTP verbs GET: data 1`] = `undefined`;
|
||||
|
||||
exports[`http - HTTP verbs PATCH: args 1`] = `Object {}`;
|
||||
|
||||
exports[`http - HTTP verbs PATCH: data 1`] = `""`;
|
||||
|
||||
exports[`http - HTTP verbs POST body and header: args 1`] = `Object {}`;
|
||||
|
||||
exports[`http - HTTP verbs POST body and header: data 1`] = `
|
||||
"1
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`http - HTTP verbs PUT: args 1`] = `Object {}`;
|
||||
|
||||
exports[`http - HTTP verbs PUT: data 1`] = `
|
||||
"1
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`https - HTTP verbs DELETE: args 1`] = `
|
||||
Object {
|
||||
"foo": "bar",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`https - HTTP verbs DELETE: data 1`] = `""`;
|
||||
|
||||
exports[`https - HTTP verbs GET: args 1`] = `
|
||||
Object {
|
||||
"param": "param_value",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`https - HTTP verbs GET: data 1`] = `undefined`;
|
||||
|
||||
exports[`https - HTTP verbs PATCH: args 1`] = `Object {}`;
|
||||
|
||||
exports[`https - HTTP verbs PATCH: data 1`] = `""`;
|
||||
|
||||
exports[`https - HTTP verbs POST body and header: args 1`] = `Object {}`;
|
||||
|
||||
exports[`https - HTTP verbs POST body and header: data 1`] = `
|
||||
"1
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`https - HTTP verbs PUT: args 1`] = `Object {}`;
|
||||
|
||||
exports[`https - HTTP verbs PUT: data 1`] = `
|
||||
"1
|
||||
"
|
||||
`;
|
247
test/basic.spec.js
Normal file
247
test/basic.spec.js
Normal file
@ -0,0 +1,247 @@
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const urllib = require('urllib');
|
||||
const request = require('request');
|
||||
const { basicProxyRequest, proxyServerWithRule, } = require('./util.js');
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
const tunnel = require('tunnel');
|
||||
|
||||
let proxyServer;
|
||||
let proxyPort;
|
||||
let proxyHost;
|
||||
let proxyWebInterfaceHost;
|
||||
beforeAll(async () => {
|
||||
jest.DEFAULT_TIMEOUT_INTERVAL = 20 * 1000;
|
||||
proxyServer = await proxyServerWithRule({}, {});
|
||||
proxyPort = proxyServer.proxyPort;
|
||||
proxyHost = `http://localhost:${proxyPort}`;
|
||||
const proxyWebInterfacePort = proxyServer.webServerInstance.webPort;
|
||||
proxyWebInterfaceHost = `http://localhost:${proxyWebInterfacePort}`;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
return proxyServer && proxyServer.close();
|
||||
});
|
||||
|
||||
function doProxyWebSocket(url, headers = {}) {
|
||||
let agent = new tunnel.httpOverHttp({
|
||||
proxy: {
|
||||
hostname: 'localhost',
|
||||
port: proxyPort,
|
||||
}
|
||||
})
|
||||
|
||||
if (url.indexOf('wss') === 0) {
|
||||
agent = new tunnel.httpsOverHttp({
|
||||
rejectUnauthorized: false,
|
||||
proxy: {
|
||||
hostname: 'localhost',
|
||||
port: proxyPort,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const ws = new WebSocket(url, {
|
||||
agent,
|
||||
rejectUnauthorized: false,
|
||||
headers
|
||||
});
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
['http', 'https'].forEach(protocol => {
|
||||
describe(`${protocol} - HTTP verbs`, () => {
|
||||
const assertProxyRes = (result) => {
|
||||
const proxyRes = result.response;
|
||||
const body = JSON.parse(result.body);
|
||||
expect(proxyRes.statusCode).toBe(200);
|
||||
expect(body.args).toMatchSnapshot('args');
|
||||
expect(body.data).toMatchSnapshot('data');
|
||||
return body;
|
||||
};
|
||||
|
||||
it('GET', async () => {
|
||||
const url = `${protocol}://httpbin.org/get`;
|
||||
const getParam = {
|
||||
param: 'param_value'
|
||||
};
|
||||
await basicProxyRequest(proxyHost, 'GET', url, {}, getParam).then(assertProxyRes);
|
||||
});
|
||||
|
||||
it('POST body and header', async () => {
|
||||
const url = `${protocol}://httpbin.org/post`;
|
||||
const payloadStream = fs.createReadStream(path.resolve(__dirname, './fixtures/upload.txt'));
|
||||
|
||||
const postHeaders = {
|
||||
anyproxy_header: 'header_value',
|
||||
};
|
||||
|
||||
const body = await basicProxyRequest(proxyHost, 'POST', url, postHeaders, {}, payloadStream).then(assertProxyRes);
|
||||
expect(body.headers['Anyproxy-Header']).toBe(postHeaders.anyproxy_header);
|
||||
});
|
||||
|
||||
it('PUT', async () => {
|
||||
const url = `${protocol}://httpbin.org/put`;
|
||||
const payloadStream = fs.createReadStream(path.resolve(__dirname, './fixtures/upload.txt'));
|
||||
await basicProxyRequest(proxyHost, 'PUT', url, {}, undefined, payloadStream).then(assertProxyRes);
|
||||
});
|
||||
|
||||
it('DELETE', async () => {
|
||||
const url = `${protocol}://httpbin.org/delete`;
|
||||
const param = {
|
||||
foo: 'bar',
|
||||
};
|
||||
await basicProxyRequest(proxyHost, 'DELETE', url, {}, param).then(assertProxyRes);
|
||||
});
|
||||
|
||||
it('PATCH', async () => {
|
||||
const url = `${protocol}://httpbin.org/patch`;
|
||||
await basicProxyRequest(proxyHost, 'PATCH', url).then(assertProxyRes);
|
||||
});
|
||||
|
||||
it('Websocket', async () => {
|
||||
const expectEcho = (ws) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const wsMsg = Buffer.alloc(100 * 1024, 'a').toString(); // 100kb
|
||||
|
||||
ws.on('open', () => {
|
||||
ws.send(wsMsg);
|
||||
});
|
||||
|
||||
ws.on('message', (msg) => {
|
||||
expect(msg).toBe(wsMsg);
|
||||
ws.close();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const wsUrl = `${protocol === 'https' ? 'wss' : 'ws'}://echo.websocket.org`;
|
||||
const ws = doProxyWebSocket(wsUrl, {});
|
||||
await expectEcho(ws);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('status code and headers', () => {
|
||||
[302, 404, 500].forEach(statusCode => {
|
||||
it(`GET ${statusCode}`, async () => {
|
||||
const status = statusCode;
|
||||
const url = `http://httpbin.org/status/${status}`;
|
||||
const result = await basicProxyRequest(proxyHost, 'GET', url, {}, {});
|
||||
const proxyRes = result.response;
|
||||
expect(proxyRes.statusCode).toBe(statusCode);
|
||||
});
|
||||
|
||||
it(`PUT ${statusCode}`, async () => {
|
||||
const status = statusCode;
|
||||
const url = `http://httpbin.org/status/${status}`;
|
||||
const result = await basicProxyRequest(proxyHost, 'PUT', url, {}, {});
|
||||
const proxyRes = result.response;
|
||||
expect(proxyRes.statusCode).toBe(statusCode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('response data formats', () => {
|
||||
['brotli', 'deflate', 'gzip'].forEach(encoding => {
|
||||
it(`GET ${encoding}`, async () => {
|
||||
const url = `http://httpbin.org/${encoding}`;
|
||||
const result = await basicProxyRequest(proxyHost, 'GET', url);
|
||||
const headers = result.response.headers;
|
||||
const body = JSON.parse(result.body);
|
||||
expect(headers['content-encoding']).toBeUndefined(); // should be removed by anyproxy
|
||||
expect(body.brotli || body.deflated || body.gzipped).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('big files', () => {
|
||||
const BIG_FILE_SIZE = 100 * 1024 * 1024 - 1; // 100 mb
|
||||
const BUFFER_FILL = 'a';
|
||||
|
||||
let server;
|
||||
beforeAll(() => {
|
||||
server = http.createServer({}, (req, res) => {
|
||||
if (/download/.test(req.url)) {
|
||||
const bufferContent = Buffer.alloc(BIG_FILE_SIZE, BUFFER_FILL);
|
||||
res.write(bufferContent);
|
||||
res.end();
|
||||
} else if (/upload/.test(req.url)) {
|
||||
let reqPayloadSize = 0;
|
||||
req.on('data', (data) => {
|
||||
const bufferLength = data.length;
|
||||
reqPayloadSize += bufferLength;
|
||||
const expectBufferContent = Buffer.alloc(bufferLength, BUFFER_FILL);
|
||||
if (!expectBufferContent.equals(data)) {
|
||||
res.statusCode = 500;
|
||||
res.write('content not match');
|
||||
}
|
||||
}).on('end', () => {
|
||||
if (res.statusCode === 500 || reqPayloadSize !== BIG_FILE_SIZE) {
|
||||
res.statusCode = 500;
|
||||
} else {
|
||||
res.statusCode = 200;
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(3000);
|
||||
});
|
||||
|
||||
afterAll((done) => {
|
||||
server && server.close(done);
|
||||
});
|
||||
|
||||
it('download big file', (done) => {
|
||||
let responseSizeCount = 0;
|
||||
request({
|
||||
url: 'http://127.0.0.1:3000/download',
|
||||
proxy: proxyHost,
|
||||
}).on('data', (data) => {
|
||||
const bufferLength = data.length;
|
||||
responseSizeCount += bufferLength;
|
||||
const expectBufferContent = Buffer.alloc(bufferLength, BUFFER_FILL);
|
||||
if (!expectBufferContent.equals(data)) {
|
||||
return done(new Error('download content not match'));
|
||||
}
|
||||
}).on('end', () => {
|
||||
if (responseSizeCount !== BIG_FILE_SIZE) {
|
||||
return done(new Error('file size not match'));
|
||||
}
|
||||
done();
|
||||
});
|
||||
}, 120 * 1000);
|
||||
|
||||
it('upload big file', (done) => {
|
||||
const bufferContent = Buffer.alloc(BIG_FILE_SIZE, BUFFER_FILL);
|
||||
const req = request({
|
||||
url: 'http://127.0.0.1:3000/upload',
|
||||
method: 'POST',
|
||||
proxy: proxyHost,
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
} else if (response.statusCode !== 200) {
|
||||
return done(new Error('upload failed ' + body));
|
||||
}
|
||||
done();
|
||||
});
|
||||
req.write(bufferContent);
|
||||
req.end();
|
||||
}, 120 * 1000);
|
||||
});
|
||||
|
||||
describe('web interface', () => {
|
||||
it('should be available', async () => {
|
||||
await urllib.request(proxyWebInterfaceHost).then((result) => {
|
||||
expect(result.status).toBe(200);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 用于放置所有header信息的测试数据
|
||||
*
|
||||
*/
|
||||
|
||||
// Get 和 Post共有的header信息
|
||||
/*eslint max-len: ["off"]*/
|
||||
const CommonRequestHeader = {
|
||||
Accept: 'application/json;charset=utf-8,text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Charset': 'utf-8',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Accept-Language': 'zh-CN',
|
||||
'Accept-Datetime': 'Thu, 31 May 2007 20:35:00 GMT',
|
||||
Authorization: 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
Cookie: 'testCookie1=cookie1; testCookie2=cookie2',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Date: 'Tue, 15 Nov 1994 08:12:31 GMT',
|
||||
Origin: 'http://localhost',
|
||||
Pragma: 'no-cache',
|
||||
some_thing: 'only_to_test_letter_case',
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
CommonRequestHeader
|
||||
};
|
||||
|
@ -1,3 +0,0 @@
|
||||
.test {
|
||||
display: block;
|
||||
}
|
Binary file not shown.
@ -1,4 +0,0 @@
|
||||
function test() {
|
||||
console.info('This is nothing but a js file, to test the js download');
|
||||
}
|
||||
test();
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
'testkey': 'this is just a normal json file'
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 103 KiB |
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata>
|
||||
<defs>
|
||||
<font id="fontello" horiz-adv-x="1000" >
|
||||
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||
<missing-glyph horiz-adv-x="1000" />
|
||||
<glyph glyph-name="glass" unicode="" d="M948 746q0-19-24-43l-353-353v-429h179q15 0 25-10t11-25-11-25-25-11h-500q-14 0-25 11t-11 25 11 25 25 10h179v429l-353 353q-24 24-24 43 0 13 10 21t21 9 24 3h786q13 0 24-3t21-9 10-21z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="music" unicode="" d="M857 725v-625q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 33 58 18 54 6q58 0 107-22v300l-429-132v-396q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 34 58 17 54 6q58 0 107-21v539q0 17 10 32t28 20l464 142q7 3 16 3 22 0 38-16t15-38z" horiz-adv-x="857.1" />
|
||||
</font>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 45 KiB |
Binary file not shown.
Binary file not shown.
3
test/fixtures/someRule.js
vendored
Normal file
3
test/fixtures/someRule.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
foo: 'bar',
|
||||
};
|
1
test/fixtures/upload.txt
vendored
Normal file
1
test/fixtures/upload.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
1
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"spec_dir": "test",
|
||||
"spec_files": [
|
||||
"spec_lib/*.js",
|
||||
"spec_rule/*.js"
|
||||
],
|
||||
"helpers": [
|
||||
"../node_modules/babel-register/lib/node.js",
|
||||
"../node_modules/babel-polyfill/dist/polyfill.js"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
const proxyTester = require('proxy-eval'),
|
||||
Buffer = require('buffer').Buffer,
|
||||
express = require('express');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.post('/', (req, res) => {
|
||||
const bigBody = new Buffer(1024 * 1024 * 10);
|
||||
res.send(bigBody); //10 mb
|
||||
});
|
||||
app.listen(3000);
|
||||
|
||||
function test() {
|
||||
//test the basic availibility of proxy server
|
||||
setTimeout(() => {
|
||||
const testParam = {
|
||||
proxy: 'http://127.0.0.1:8001/',
|
||||
reqTimeout: 4500,
|
||||
httpGetUrl: '',
|
||||
httpPostUrl: 'http://127.0.0.1:3000/',
|
||||
httpPostBody: '123',
|
||||
httpsGetUrl: '',
|
||||
httpsPostUrl: '',
|
||||
httpsPostBody: ''
|
||||
};
|
||||
proxyTester.test(testParam, (results) => {
|
||||
process.exit();
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
test();
|
||||
}, 3000);
|
||||
|
67
test/lib/httpsServerMgr.spec.js
Normal file
67
test/lib/httpsServerMgr.spec.js
Normal file
@ -0,0 +1,67 @@
|
||||
const tls = require('tls');
|
||||
const httpsServerMgr = require('../../lib/httpsServerMgr');
|
||||
|
||||
describe('httpsServerMgr', () => {
|
||||
let serverMgrInstance;
|
||||
|
||||
beforeAll(async () => {
|
||||
serverMgrInstance = new httpsServerMgr({
|
||||
hostname: '127.0.0.1',
|
||||
handler: (req, res) => {
|
||||
res.end('hello world');
|
||||
},
|
||||
wsHandler: () => { },
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await serverMgrInstance.close();
|
||||
});
|
||||
|
||||
it('SNI server should work properly', async () => {
|
||||
const sniServerA = await serverMgrInstance.getSharedHttpsServer('a.anyproxy.io');
|
||||
const sniServerB = await serverMgrInstance.getSharedHttpsServer('b.anyproxy.io');
|
||||
|
||||
expect(sniServerA).toEqual(sniServerB); // SNI - common server
|
||||
|
||||
const connectHostname = 'some_new_host.anyproxy.io';
|
||||
const connectOpt = {
|
||||
servername: connectHostname, // servername is required for sni server
|
||||
rejectUnauthorized: false,
|
||||
}
|
||||
await new Promise((resolve, reject) => {
|
||||
const socketToSNIServer = tls.connect(sniServerA.port, '127.0.0.1', connectOpt, (tlsSocket) => {
|
||||
// console.log('client to SNI server connected, ', socketToSNIServer.authorized ? 'authorized' : 'unauthorized');
|
||||
const certSubject = socketToSNIServer.getPeerCertificate().subject;
|
||||
expect(certSubject.CN).toEqual(connectHostname);
|
||||
socketToSNIServer.end();
|
||||
resolve();
|
||||
});
|
||||
|
||||
socketToSNIServer.on('keylog', line => {
|
||||
console.log(line);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('IP server should work properly', async () => {
|
||||
const ipServerHost = '1.2.3.4';
|
||||
const anotherSNIServer = await serverMgrInstance.getSharedHttpsServer('c.anyproxy.io');
|
||||
const ipServerA = await serverMgrInstance.getSharedHttpsServer(ipServerHost);
|
||||
const ipServerB = await serverMgrInstance.getSharedHttpsServer('5.6.7.8');
|
||||
expect(ipServerA).not.toEqual(ipServerB);
|
||||
expect(anotherSNIServer).not.toEqual(ipServerA);
|
||||
|
||||
const connectOpt = {
|
||||
rejectUnauthorized: false,
|
||||
}
|
||||
await new Promise((resolve, reject) => {
|
||||
const socketToIpServer = tls.connect(ipServerA.port, '127.0.0.1', connectOpt, () => {
|
||||
const certSubject = socketToIpServer.getPeerCertificate().subject;
|
||||
expect(certSubject.CN).toEqual(ipServerHost);
|
||||
socketToIpServer.end();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,34 +1,25 @@
|
||||
/*
|
||||
* test for rule replaceOption rule
|
||||
*
|
||||
*/
|
||||
|
||||
const ruleLoader = require('../../lib/ruleLoader');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const localModulePath = path.join(__dirname, '../util/CommonUtil.js');
|
||||
describe('rule loader', () => {
|
||||
it('should successfully cache a remote file', done => {
|
||||
ruleLoader.cacheRemoteFile('https://cdn.bootcss.com/lodash.js/4.16.4/lodash.min.js')
|
||||
const localModulePath = path.join(__dirname, '../fixtures/someRule.js');
|
||||
describe('ruleLoader', () => {
|
||||
it('should successfully cache a remote file', async () => {
|
||||
await ruleLoader.cacheRemoteFile('https://cdn.bootcss.com/lodash.js/4.16.4/lodash.min.js')
|
||||
.then(filePath => {
|
||||
let content;
|
||||
if (filePath) {
|
||||
content = fs.readFileSync(filePath, { encoding: 'utf8' });
|
||||
}
|
||||
expect(content && content.length > 100).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load a local module ../util/CommonUtil', done => {
|
||||
ruleLoader.loadLocalPath(localModulePath)
|
||||
it('should load a local module ../util/CommonUtil', async () => {
|
||||
await ruleLoader.loadLocalPath(localModulePath)
|
||||
.then(module => {
|
||||
expect(module.printLog).not.toBeUndefined();
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
expect(module.foo).not.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should smart load a remote module', done => {
|
||||
@ -43,7 +34,7 @@ describe('rule loader', () => {
|
||||
it('should smart load a local module', done => {
|
||||
ruleLoader.requireModule(localModulePath)
|
||||
.then(module => {
|
||||
expect(module.printLog).not.toBeUndefined();
|
||||
expect(module.foo).not.toBeUndefined();
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
@ -1,17 +1,13 @@
|
||||
/*
|
||||
* test for rule replaceOption rule
|
||||
*
|
||||
*/
|
||||
const util = require('../../lib/util');
|
||||
|
||||
describe('utils', () => {
|
||||
it('should get some free ports', done => {
|
||||
it('getFreePort', async () => {
|
||||
const count = 100;
|
||||
const tasks = [];
|
||||
for (let i = 1; i <= count; i++) {
|
||||
tasks.push(util.getFreePort());
|
||||
}
|
||||
Promise.all(tasks)
|
||||
await Promise.all(tasks)
|
||||
.then((results) => {
|
||||
// ensure ports are unique
|
||||
const portMap = {};
|
||||
@ -19,8 +15,6 @@ describe('utils', () => {
|
||||
portMap[portNumber] = true;
|
||||
});
|
||||
expect(Object.keys(portMap).length).toEqual(count);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
@ -1 +0,0 @@
|
||||
* this is a folder to save test reports *
|
50
test/rule/beforeDealHttpsRequest.spec.js
Normal file
50
test/rule/beforeDealHttpsRequest.spec.js
Normal file
@ -0,0 +1,50 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { basicProxyRequest, proxyServerWithRule, } = require('../util.js');
|
||||
|
||||
const RULE_PAYLOAD = 'this is something in rule';
|
||||
|
||||
const rule = {
|
||||
*beforeSendRequest(requestDetail) {
|
||||
const requestOptions = requestDetail.requestOptions;
|
||||
return {
|
||||
requestOptions,
|
||||
requestData: RULE_PAYLOAD,
|
||||
};
|
||||
},
|
||||
|
||||
*beforeDealHttpsRequest(requestDetail) {
|
||||
return requestDetail.host.indexOf('httpbin.org') >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
describe('Rule beforeDealHttpsRequest', () => {
|
||||
let proxyServer;
|
||||
let proxyPort;
|
||||
let proxyHost;
|
||||
|
||||
beforeAll(async () => {
|
||||
proxyServer = await proxyServerWithRule(rule);
|
||||
proxyPort = proxyServer.proxyPort;
|
||||
proxyHost = `http://localhost:${proxyPort}`;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
return proxyServer && proxyServer.close();
|
||||
});
|
||||
it('Should replace the https request body', async () => {
|
||||
const url = 'https://httpbin.org/put';
|
||||
const payloadStream = fs.createReadStream(path.resolve(__dirname, '../fixtures/upload.txt'));
|
||||
const postHeaders = {
|
||||
anyproxy_header: 'header_value',
|
||||
};
|
||||
|
||||
await basicProxyRequest(proxyHost, 'PUT', url, postHeaders, {}, payloadStream).then((result) => {
|
||||
const proxyRes = result.response;
|
||||
const body = JSON.parse(result.body);
|
||||
expect(proxyRes.statusCode).toBe(200);
|
||||
expect(body.data).toEqual(RULE_PAYLOAD);
|
||||
expect(body.url.indexOf('/put')).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
93
test/rule/beforeSendRequest.spec.js
Normal file
93
test/rule/beforeSendRequest.spec.js
Normal file
@ -0,0 +1,93 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { basicProxyRequest, proxyServerWithRule, } = require('../util.js');
|
||||
|
||||
const RULE_PAYLOAD = 'this is something in rule';
|
||||
const RULE_REPLACE_HEADER_KEY = 'rule_replace_header_key';
|
||||
const RULE_REPLACE_HEADER_VALUE = 'rule_replace_header_value';
|
||||
|
||||
const rule = {
|
||||
*beforeSendRequest(requestDetail) {
|
||||
const reqUrl = requestDetail.url;
|
||||
if (reqUrl.indexOf('/post') >= 0) {
|
||||
const requestOptions = requestDetail.requestOptions;
|
||||
requestOptions.path = '/put';
|
||||
requestOptions.method = 'PUT';
|
||||
return {
|
||||
requestOptions,
|
||||
requestData: RULE_PAYLOAD,
|
||||
};
|
||||
} else if (reqUrl.indexOf('/status/302') >= 0) {
|
||||
return {
|
||||
response: {
|
||||
statusCode: 404,
|
||||
header: {
|
||||
[RULE_REPLACE_HEADER_KEY]: RULE_REPLACE_HEADER_VALUE,
|
||||
'content-type': 'plain/text',
|
||||
},
|
||||
body: RULE_PAYLOAD
|
||||
}
|
||||
};
|
||||
} else if (reqUrl.indexOf('/should_be_replaced') >= 0) {
|
||||
const requestOptions = requestDetail.requestOptions;
|
||||
requestOptions.hostname = 'httpbin.org';
|
||||
requestOptions.path = '/status/302';
|
||||
requestOptions.port = '443';
|
||||
return {
|
||||
protocol: 'https',
|
||||
requestOptions,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('Rule replaceRequestData', () => {
|
||||
let proxyServer;
|
||||
let proxyPort;
|
||||
let proxyHost;
|
||||
|
||||
beforeAll(async () => {
|
||||
proxyServer = await proxyServerWithRule(rule);
|
||||
proxyPort = proxyServer.proxyPort;
|
||||
proxyHost = `http://localhost:${proxyPort}`;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
return proxyServer && proxyServer.close();
|
||||
});
|
||||
|
||||
it('should replace the request data in proxy if the assertion is true', async () => {
|
||||
const url = 'http://httpbin.org/post';
|
||||
const payloadStream = fs.createReadStream(path.resolve(__dirname, '../fixtures/upload.txt'));
|
||||
const postHeaders = {
|
||||
anyproxy_header: 'header_value',
|
||||
};
|
||||
|
||||
await basicProxyRequest(proxyHost, 'POST', url, postHeaders, {}, payloadStream).then((result) => {
|
||||
const proxyRes = result.response;
|
||||
const body = JSON.parse(result.body);
|
||||
expect(proxyRes.statusCode).toBe(200);
|
||||
expect(body.data).toEqual(RULE_PAYLOAD);
|
||||
expect(body.url.indexOf('/put')).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond content specified in rule', async () => {
|
||||
const url = 'http://httpbin.org/status/302';
|
||||
await basicProxyRequest(proxyHost, 'GET', url).then((result) => {
|
||||
const proxyRes = result.response;
|
||||
const body = result.body;
|
||||
expect(body).toBe(RULE_PAYLOAD);
|
||||
expect(proxyRes.statusCode).toBe(404);
|
||||
expect(proxyRes.headers[RULE_REPLACE_HEADER_KEY]).toBe(RULE_REPLACE_HEADER_VALUE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace protocol and url', async () => {
|
||||
const url = 'http://domain_not_exists.anyproxy.io/should_be_replaced';
|
||||
await basicProxyRequest(proxyHost, 'GET', url).then((result) => {
|
||||
const proxyRes = result.response;
|
||||
expect(proxyRes.statusCode).toBe(302);
|
||||
});
|
||||
});
|
||||
});
|
45
test/rule/beforeSendResponse.js
Normal file
45
test/rule/beforeSendResponse.js
Normal file
@ -0,0 +1,45 @@
|
||||
const { basicProxyRequest, proxyServerWithRule, } = require('../util.js');
|
||||
|
||||
const RULE_REPLACE_HEADER_KEY = 'rule_replace_header_key';
|
||||
const RULE_REPLACE_HEADER_VALUE = 'rule_replace_header_value';
|
||||
const RULE_REPLACE_BODY = 'RULE_REPLACE_BODY';
|
||||
const rule = {
|
||||
*beforeSendResponse(requestDetail, responseDetail) {
|
||||
if (requestDetail.url.indexOf('/uuid') >= 0) {
|
||||
const newResponse = responseDetail.response;
|
||||
newResponse.header[RULE_REPLACE_HEADER_KEY] = RULE_REPLACE_HEADER_VALUE;
|
||||
newResponse.body = RULE_REPLACE_BODY;
|
||||
newResponse.statusCode = 502;
|
||||
return {
|
||||
response: newResponse,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
describe('Rule replaceResponseData', () => {
|
||||
let proxyServer;
|
||||
let proxyPort;
|
||||
let proxyHost;
|
||||
|
||||
beforeAll(async () => {
|
||||
proxyServer = await proxyServerWithRule(rule);
|
||||
proxyPort = proxyServer.proxyPort;
|
||||
proxyHost = `http://localhost:${proxyPort}`;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
return proxyServer && proxyServer.close();
|
||||
});
|
||||
|
||||
it('Should replace the header and body', async () => {
|
||||
const url = 'http://httpbin.org/uuid';
|
||||
await basicProxyRequest(proxyHost, 'GET', url).then((result) => {
|
||||
const proxyRes = result.response;
|
||||
const body = result.body;
|
||||
expect(proxyRes.statusCode).toBe(502);
|
||||
expect(proxyRes.headers[RULE_REPLACE_HEADER_KEY]).toBe(RULE_REPLACE_HEADER_VALUE);
|
||||
expect(body).toBe(RULE_REPLACE_BODY);
|
||||
});
|
||||
});
|
||||
});
|
60
test/rule/onError.spec.js
Normal file
60
test/rule/onError.spec.js
Normal file
@ -0,0 +1,60 @@
|
||||
const { basicProxyRequest, proxyServerWithRule, } = require('../util.js');
|
||||
|
||||
const jestMockErrorFn = jest.fn();
|
||||
const jestMockConnectErrorFn = jest.fn();
|
||||
|
||||
const ERROR_PAGE_IN_RULE = 'this is my error page';
|
||||
const rule = {
|
||||
onConnectError: jestMockConnectErrorFn,
|
||||
*onError(requestDetail, error) {
|
||||
jestMockErrorFn(requestDetail, error);
|
||||
return {
|
||||
response: {
|
||||
statusCode: '200',
|
||||
header: {},
|
||||
body: ERROR_PAGE_IN_RULE,
|
||||
}
|
||||
};
|
||||
},
|
||||
*beforeDealHttpsRequest(requestDetail) {
|
||||
return requestDetail.host.indexOf('intercept') === 0;
|
||||
},
|
||||
};
|
||||
|
||||
describe('Rule replaceResponseData', () => {
|
||||
let proxyServer;
|
||||
let proxyPort;
|
||||
let proxyHost;
|
||||
|
||||
beforeAll(async () => {
|
||||
proxyServer = await proxyServerWithRule(rule);
|
||||
proxyPort = proxyServer.proxyPort;
|
||||
proxyHost = `http://localhost:${proxyPort}`;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
return proxyServer && proxyServer.close();
|
||||
});
|
||||
|
||||
it('should get error', async () => {
|
||||
const url = 'https://intercept.anyproxy_not_exists.io/some_path';
|
||||
const result = await basicProxyRequest(proxyHost, 'GET', url);
|
||||
const proxyRes = result.response;
|
||||
const body = result.body;
|
||||
expect(proxyRes.statusCode).toBe(200);
|
||||
expect(body).toBe(ERROR_PAGE_IN_RULE);
|
||||
expect(jestMockErrorFn.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should get connec error', async () => {
|
||||
const url = 'https://anyproxy_not_exists.io/do_not_intercept';
|
||||
let e;
|
||||
try {
|
||||
await basicProxyRequest(proxyHost, 'GET', url);
|
||||
} catch (err) {
|
||||
e = err;
|
||||
}
|
||||
expect(e).not.toBeUndefined();
|
||||
expect(jestMockConnectErrorFn.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
@ -1,347 +0,0 @@
|
||||
const Koa = require('koa');
|
||||
const KoaRouter = require('koa-router');
|
||||
const koaBody = require('koa-body');
|
||||
const send = require('koa-send');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const certMgr = require('../../lib/certMgr');
|
||||
const fs = require('fs');
|
||||
const nurl = require('url');
|
||||
const websocket = require('koa-websocket');
|
||||
const color = require('colorful');
|
||||
const WebSocketServer = require('ws').Server;
|
||||
const tls = require('tls');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const createSecureContext = tls.createSecureContext || crypto.createSecureContext;
|
||||
|
||||
const DEFAULT_PORT = 3000;
|
||||
const HTTPS_PORT = 3001;
|
||||
const HTTPS_PORT2 = 3002; // start multiple https server
|
||||
const UPLOAD_DIR = path.resolve(__dirname, '../temp');
|
||||
const PROXY_KEY_PREFIX = 'proxy-';
|
||||
|
||||
function SNICertCallback(serverName, SNICallback) {
|
||||
certMgr.getCertificate(serverName, (err, key, crt) => {
|
||||
if (err) {
|
||||
console.error('error happend in sni callback', err);
|
||||
return;
|
||||
}
|
||||
const ctx = createSecureContext({
|
||||
key,
|
||||
cert: crt
|
||||
});
|
||||
|
||||
SNICallback(null, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
function KoaServer() {
|
||||
this.httpServer = null;
|
||||
this.httpsServer = null;
|
||||
this.requestRecordMap = {}; // store all request data to the map
|
||||
const self = this;
|
||||
|
||||
/**
|
||||
* log the request info, write as
|
||||
*/
|
||||
this.logRequest = function *(next) {
|
||||
const headers = this.request.headers;
|
||||
let key = this.request.protocol + '://' + this.request.host + nurl.parse(this.request.url).pathname; // remove param to get clean key
|
||||
|
||||
// take proxy data with 'proxy-' + url
|
||||
if (headers['via-proxy'] === 'true') {
|
||||
key = PROXY_KEY_PREFIX + key;
|
||||
}
|
||||
|
||||
printLog('log request with key :' + key);
|
||||
let body = this.request.body;
|
||||
body = typeof body === 'object' ? JSON.stringify(body) : body;
|
||||
|
||||
self.requestRecordMap[key] = {
|
||||
headers,
|
||||
body
|
||||
};
|
||||
yield next;
|
||||
};
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
KoaServer.prototype.constructRouter = function () {
|
||||
const router = KoaRouter();
|
||||
router.post('/test/getuser', koaBody(), this.logRequest, function *(next) {
|
||||
printLog('requesting post /test/getuser');
|
||||
this.response.set('reqbody', JSON.stringify(this.request.body));
|
||||
this.response.body = 'body_post_getuser';
|
||||
});
|
||||
|
||||
router.get('/test', this.logRequest, function *(next) {
|
||||
printLog('request in get: ' + JSON.stringify(this.request));
|
||||
this.cookies.set('a1', 'a1value');
|
||||
this.cookies.set('a2', 'a2value');
|
||||
this.cookies.set('a3', 'a3value');
|
||||
this.response.set('header1', 'cookie2=headervalue2');
|
||||
|
||||
this.response.body = 'something';
|
||||
this.response.__req = this.request;
|
||||
printLog('response in get:' + JSON.stringify(this.response));
|
||||
});
|
||||
|
||||
router.get('/test/uselocal', this.logRequest, function *(next) {
|
||||
printLog('request in get local:' + JSON.stringify(this.request));
|
||||
this.response.body = 'something should be in local';
|
||||
// this.response.__req = this.request;
|
||||
printLog('response in get:' + JSON.stringify(this.response));
|
||||
});
|
||||
|
||||
['png', 'webp', 'json', 'js', 'css', 'ttf', 'eot', 'svg', 'woff', 'woff2'].forEach(item => {
|
||||
router.get(`/test/download/${item}`, this.logRequest, function *(next) {
|
||||
yield send(this, `./data/test.${item}`, {
|
||||
root: path.resolve(__dirname, '../')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/test/response/304', this.logRequest, function *(next) {
|
||||
this.response.set('Content-Encoding', 'gzip');
|
||||
this.status = 304;
|
||||
});
|
||||
|
||||
router.get('/test/response/303', function *(next) {
|
||||
printLog('now to redirect 303');
|
||||
this.redirect('/test');
|
||||
this.status = 303;
|
||||
});
|
||||
|
||||
router.get('/test/response/302', function *(next) {
|
||||
printLog('now to redirect 302');
|
||||
this.redirect('/test');
|
||||
});
|
||||
|
||||
router.get('/test/response/301', function *(next) {
|
||||
printLog('now to redirect permanently');
|
||||
this.redirect('/test');
|
||||
this.status = 301;
|
||||
});
|
||||
|
||||
const onFileBegin = function (name, file) {
|
||||
if (!fs.existsSync(UPLOAD_DIR)) {
|
||||
try {
|
||||
fs.mkdirSync(UPLOAD_DIR, '0777');
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
file.name = 'test_upload_' + Date.now() + '.png';
|
||||
const folder = path.dirname(file.path);
|
||||
file.path = path.join(folder, file.name);
|
||||
};
|
||||
|
||||
router.post('/test/upload/png',
|
||||
this.logRequest,
|
||||
koaBody({
|
||||
multipart: true,
|
||||
formidable: {
|
||||
uploadDir: UPLOAD_DIR,
|
||||
onFileBegin
|
||||
}
|
||||
}),
|
||||
function *(next) {
|
||||
const file = this.request.body.files.file;
|
||||
this.response.set('reqbody', JSON.stringify(this.request.body.fields));
|
||||
this.response.body = file.path;
|
||||
}
|
||||
);
|
||||
|
||||
router.put('/test/upload/putpng',
|
||||
this.logRequest,
|
||||
koaBody({
|
||||
multipart: true,
|
||||
formidable: {
|
||||
uploadDir: UPLOAD_DIR,
|
||||
onFileBegin
|
||||
}
|
||||
}),
|
||||
function *(next) {
|
||||
const file = this.request.body.files.file;
|
||||
this.response.body = file.path;
|
||||
}
|
||||
);
|
||||
|
||||
router.put('/test/put', koaBody(), this.logRequest, function *(next) {
|
||||
printLog('requesting put /test/put' + JSON.stringify(this.request));
|
||||
this.response.body = 'something in put';
|
||||
});
|
||||
|
||||
router.delete('/test/delete/:id', this.logRequest, function *(next) {
|
||||
printLog('requesting delete /test/delete/:id' + JSON.stringify(this.params));
|
||||
this.response.body = 'something in delete';
|
||||
});
|
||||
|
||||
router.head('/test/head', this.logRequest, function *(next) {
|
||||
printLog('requesting head /test/head');
|
||||
this.response.body = ''; // the body will not be passed to response, in HEAD request
|
||||
this.response.set('reqBody', 'head_request_contains_no_resbody');
|
||||
});
|
||||
|
||||
router.options('/test/options', this.logRequest, function *(next) {
|
||||
printLog('requesting options /test/options');
|
||||
this.response.body = 'could_be_empty';
|
||||
this.response.set('Allow', 'GET, HEAD, POST, OPTIONS');
|
||||
});
|
||||
|
||||
// router.connect('/test/connect', function *(next) {
|
||||
// printLog('requesting connect /test/connect');
|
||||
// this.response.body = 'connect_established_body';
|
||||
// });
|
||||
|
||||
router.get('/test/should_not_replace_option', this.logRequest, function *(next) {
|
||||
this.response.body = 'the_option_that_not_be_replaced';
|
||||
});
|
||||
|
||||
router.get('/test/should_replace_option', this.logRequest, function *(next) {
|
||||
this.response.body = 'the_request_that_has_not_be_replaced';
|
||||
});
|
||||
|
||||
router.get('/test/new_replace_option', this.logRequest, function *(next) {
|
||||
this.response.body = 'the_new_replaced_option_page_content';
|
||||
});
|
||||
|
||||
router.get('/test/normal_request1', this.logRequest, koaBody(), function *(next) {
|
||||
printLog('requesting get /test/normal_request1');
|
||||
this.response.body = 'body_normal_request1';
|
||||
});
|
||||
|
||||
router.get('/test/normal_request2', this.logRequest, koaBody(), function *(next) {
|
||||
printLog('requesting get /test/normal_request2');
|
||||
this.response.body = 'body_normal_request2';
|
||||
});
|
||||
|
||||
router.post('/test/normal_post_request1', koaBody(), this.logRequest, function *(next) {
|
||||
printLog('requesting post /test/normal_post_request1');
|
||||
this.response.body = 'body_normal_post_request1';
|
||||
});
|
||||
|
||||
router.get('/big_response', this.logRequest, function *(next) {
|
||||
const buf = new Buffer(1 * 1024 * 1024 * 1024); // 1GB
|
||||
buf.fill(1);
|
||||
printLog('request in get big response of 1GB');
|
||||
this.response.type = 'application/octet-stream';
|
||||
this.response.body = buf;
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
KoaServer.prototype.constructWsRouter = function () {
|
||||
const wsRouter = KoaRouter();
|
||||
const self = this;
|
||||
wsRouter.get('/test/socket', function *(next) {
|
||||
const ws = this.websocket;
|
||||
const messageObj = {
|
||||
type: 'initial',
|
||||
content: 'default message'
|
||||
};
|
||||
|
||||
ws.send(JSON.stringify(messageObj));
|
||||
ws.on('message', message => {
|
||||
printLog('message from request socket: ' + message);
|
||||
self.handleRecievedMessage(ws, message);
|
||||
});
|
||||
yield next;
|
||||
});
|
||||
|
||||
return wsRouter;
|
||||
};
|
||||
|
||||
KoaServer.prototype.getRequestRecord = function (key) {
|
||||
return this.requestRecordMap[key] || null;
|
||||
};
|
||||
|
||||
KoaServer.prototype.getProxyRequestRecord = function (key) {
|
||||
key = PROXY_KEY_PREFIX + key;
|
||||
return this.requestRecordMap[key] || null;
|
||||
};
|
||||
|
||||
KoaServer.prototype.handleRecievedMessage = function (ws, message) {
|
||||
const newMessage = {
|
||||
type: 'onMessage',
|
||||
content: message
|
||||
};
|
||||
ws.send(JSON.stringify(newMessage));
|
||||
};
|
||||
|
||||
KoaServer.prototype.start = function () {
|
||||
printLog('Starting the server...');
|
||||
const router = this.constructRouter();
|
||||
const wsRouter = this.constructWsRouter();
|
||||
const self = this;
|
||||
const app = Koa();
|
||||
websocket(app);
|
||||
|
||||
app.use(router.routes());
|
||||
app.ws.use(wsRouter.routes());
|
||||
this.httpServer = app.listen(DEFAULT_PORT);
|
||||
|
||||
printLog('HTTP is now listening on port :' + DEFAULT_PORT);
|
||||
|
||||
certMgr.getCertificate('localhost', (error, keyContent, crtContent) => {
|
||||
if (error) {
|
||||
console.error('failed to create https server:', error);
|
||||
} else {
|
||||
self.httpsServer = https.createServer({
|
||||
SNICallback: SNICertCallback,
|
||||
key: keyContent,
|
||||
cert: crtContent
|
||||
}, app.callback());
|
||||
|
||||
// create wss server
|
||||
const wss = new WebSocketServer({
|
||||
server: self.httpsServer
|
||||
});
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
ws.on('message', (message) => {
|
||||
printLog('received in wss: ' + message);
|
||||
self.handleRecievedMessage(ws, message);
|
||||
});
|
||||
});
|
||||
|
||||
wss.on('error', e => console.error('error happened in wss:%s', e));
|
||||
|
||||
self.httpsServer.listen(HTTPS_PORT);
|
||||
|
||||
self.httpsServer2 = https.createServer({
|
||||
key: keyContent,
|
||||
cert: crtContent
|
||||
}, app.callback());
|
||||
|
||||
self.httpsServer2.listen(HTTPS_PORT2);
|
||||
|
||||
printLog('HTTPS is now listening on port :' + HTTPS_PORT);
|
||||
|
||||
printLog('Server started successfully');
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
KoaServer.prototype.close = function () {
|
||||
printLog('Closing server now...');
|
||||
this.httpServer && this.httpServer.close();
|
||||
this.httpsServer && this.httpsServer.close();
|
||||
this.httpsServer2 && this.httpsServer2.close();
|
||||
this.requestRecordMap = {};
|
||||
printLog('Server closed successfully');
|
||||
};
|
||||
|
||||
|
||||
function printLog(content) {
|
||||
console.log(color.cyan('[SERVER LOG]: ' + content));
|
||||
}
|
||||
|
||||
module.exports = KoaServer;
|
@ -1,3 +0,0 @@
|
||||
const Server = require('./server.js');
|
||||
|
||||
new Server();
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* test for rule replaceOption rule
|
||||
*
|
||||
*/
|
||||
const ip = require('ip');
|
||||
const AnyProxy = require('../../proxy');
|
||||
const { proxyGet, directGet } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
|
||||
const OUT_BOUND_IP = ip.address();
|
||||
|
||||
describe('AnyProxy.proxyServer basic test', () => {
|
||||
it('should successfully start a proxy server', done => {
|
||||
const options = {
|
||||
port: 8001,
|
||||
rule: null,
|
||||
webInterface: {
|
||||
enable: true,
|
||||
webPort: 8002
|
||||
},
|
||||
throttle: 10000,
|
||||
forceProxyHttps: false,
|
||||
silent: false
|
||||
};
|
||||
const proxyServer = new AnyProxy.ProxyServer(options);
|
||||
proxyServer.on('ready', () => {
|
||||
proxyServer.close();
|
||||
done();
|
||||
});
|
||||
proxyServer.on('error', done.fail);
|
||||
proxyServer.start();
|
||||
});
|
||||
});
|
||||
|
||||
describe('AnyProxy.proxyServer high order test', () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
beforeAll(done => {
|
||||
// jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
|
||||
serverInstance = new Server();
|
||||
|
||||
const options = {
|
||||
port: 8001,
|
||||
rule: null,
|
||||
webInterface: {
|
||||
enable: true,
|
||||
webPort: 8002,
|
||||
},
|
||||
throttle: 10000,
|
||||
forceProxyHttps: false,
|
||||
silent: false
|
||||
};
|
||||
proxyServer = new AnyProxy.ProxyServer(options);
|
||||
proxyServer.on('ready', done);
|
||||
proxyServer.start();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
proxyServer && proxyServer.close();
|
||||
serverInstance && serverInstance.close();
|
||||
});
|
||||
|
||||
it('should work as expected for domain host', done => {
|
||||
// test if proxy server works
|
||||
proxyGet('https://www.tmall.com', {}, {})
|
||||
.then(res => {
|
||||
expect(res && res.statusCode && res.statusCode === 200 && res.body.length > 300).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(done)
|
||||
});
|
||||
|
||||
it('should work as expected for ip host', done => {
|
||||
// test if proxy server works
|
||||
proxyGet(`https://${OUT_BOUND_IP}:3001/test`, {}, {})
|
||||
.then(res => {
|
||||
expect(res && res.statusCode && res.statusCode === 200).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(done)
|
||||
});
|
||||
|
||||
it('should start webinterface correctly', done => {
|
||||
// test web interface
|
||||
directGet('http://127.0.0.1:8002', {}, {})
|
||||
.then(res => {
|
||||
expect(res && res.statusCode && res.statusCode === 200 && res.body.length > 300).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(done)
|
||||
});
|
||||
});
|
@ -1,147 +0,0 @@
|
||||
|
||||
/**
|
||||
* use phantomjs to capture requests in real websites, then compare the directly-connected response with those through AnyProxy
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const HttpUtil = require('../util/HttpUtil.js');
|
||||
const path = require('path');
|
||||
const { printLog, printError, printHilite, stringSimilarity } = require('../util/CommonUtil.js');
|
||||
|
||||
const reportPath = path.join(__dirname, '../report/');
|
||||
|
||||
const testUrls = ['https://www.taobao.com', 'https://www.baidu.com', 'https://www.tmall.com'];
|
||||
|
||||
let direcrtResponseSampleA = [];
|
||||
let direcrtResponseSampleB = [];
|
||||
let proxyResponse = [];
|
||||
|
||||
function test(url, requestHeaders = {}) {
|
||||
fdescribe('Test requests in real broswer', () => {
|
||||
let proxyServer;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000;
|
||||
printLog('Start server for ' + url);
|
||||
|
||||
proxyServer = ProxyServerUtil.defaultProxyServer();
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Closed server for ' + url);
|
||||
});
|
||||
|
||||
it(`Request towards ${url}`, (done) => {
|
||||
HttpUtil.getRequestListFromPage(url).then((arr) => {
|
||||
const directPromisesA = [];
|
||||
const directPromisesB = [];
|
||||
const proxyPromises = [];
|
||||
arr.forEach((data, i) => {
|
||||
const requestPath = data.url;
|
||||
const headers = data.headers;
|
||||
const method = data.method;
|
||||
const params = data.method === 'POST' ? JSON.parse(data.postData) : {};
|
||||
if (HttpUtil.isSupportedProtocol(requestPath)) {
|
||||
directPromisesA.push(HttpUtil.directRequest(method, requestPath, params, headers));
|
||||
directPromisesB.push(HttpUtil.directRequest(method, requestPath, params, headers));
|
||||
proxyPromises.push(HttpUtil.proxyRequest(method, requestPath, params, headers));
|
||||
}
|
||||
});
|
||||
Promise.all(directPromisesA).then(responseArr => { direcrtResponseSampleA = responseArr }).then(() => {
|
||||
Promise.all(directPromisesB).then(responseArr => { direcrtResponseSampleB = responseArr }).then(() => {
|
||||
Promise.all(proxyPromises).then(responseArr => { proxyResponse = responseArr }).then(() => {
|
||||
showResponseResult();
|
||||
const { compareResult: TESTRESULT, errRecord } = compareResponses(url);
|
||||
const reportFile = dealLogFile(errRecord, url, () => {
|
||||
printHilite('====== COMPARE RESULT: ' + TESTRESULT.toString().toUpperCase() + ' ======');
|
||||
!TESTRESULT && printHilite(`Check more details in ${reportFile}`);
|
||||
// expect(TESTRESULT).toBe(true);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
printError(err);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
printError(err);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
printError(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function compareResponses(curUrl) {
|
||||
const errRecord = [];
|
||||
if (direcrtResponseSampleA.length !== direcrtResponseSampleB.length || direcrtResponseSampleA.length !== proxyResponse.length) {
|
||||
printError('compare fail: length not match');
|
||||
return {
|
||||
compareResult: false,
|
||||
errRecord
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < proxyResponse.length; i++) {
|
||||
const direcrtResponseInfoA = direcrtResponseSampleA[i];
|
||||
const direcrtResponseInfoB = direcrtResponseSampleB[i];
|
||||
const proxyResponseInfo = proxyResponse[i];
|
||||
const { similarity: similarity_direct } = stringSimilarity(stringify(direcrtResponseInfoA.body), stringify(direcrtResponseInfoB.body));
|
||||
const { similarity: similarity_proxy } = stringSimilarity(stringify(direcrtResponseInfoA.body), stringify(proxyResponseInfo.body));
|
||||
if (similarity_direct !== similarity_proxy) {
|
||||
let LogText = `Similarity from ${proxyResponseInfo.request.href} between direct samples and proxy is not equal : ${similarity_direct} | ${similarity_proxy}\n`;
|
||||
printError(LogText);
|
||||
LogText += `\n${stringify(direcrtResponseInfoA.body)}`;
|
||||
LogText += '\n=============================================\n';
|
||||
LogText += `${stringify(direcrtResponseInfoB.body)}`;
|
||||
LogText += '\n=============================================\n';
|
||||
LogText += `${stringify(proxyResponseInfo.body)}\n\n`;
|
||||
errRecord.push(LogText);
|
||||
}
|
||||
}
|
||||
return {
|
||||
compareResult: errRecord.length === 0,
|
||||
errRecord
|
||||
};
|
||||
}
|
||||
|
||||
function stringify(data) {
|
||||
return data ? data.replace(/\s+/g, '') : '';
|
||||
}
|
||||
|
||||
function showResponseResult() {
|
||||
if (direcrtResponseSampleA.length !== direcrtResponseSampleB.length || direcrtResponseSampleA.length !== proxyResponse.length) {
|
||||
printError('compare fail: length not match');
|
||||
}
|
||||
proxyResponse.forEach((dataObj, i) => {
|
||||
const direcrtResponseInfoA = direcrtResponseSampleA[i];
|
||||
const direcrtResponseInfoB = direcrtResponseSampleB[i];
|
||||
printLog(`Direct Sample A ${direcrtResponseInfoA.request.method}: ${direcrtResponseInfoA.request.href} ${direcrtResponseInfoA.statusCode}${direcrtResponseInfoA.statusMessage}`);
|
||||
printLog(`Direct Sample B ${direcrtResponseInfoB.request.method}: ${direcrtResponseInfoB.request.href} ${direcrtResponseInfoB.statusCode}${direcrtResponseInfoB.statusMessage}`);
|
||||
printLog(`PROXY ${dataObj.request.method}: ${dataObj.request.href} ${dataObj.statusCode}${dataObj.statusMessage}`);
|
||||
})
|
||||
console.log('\n');
|
||||
}
|
||||
|
||||
function dealLogFile(dataObj = 'Log', url, cb) {
|
||||
const filePath = reportPath + url.replace(/[^\u4E00-\u9FA5A-Za-z\s()()\d•·]/g, '_') + '.txt';
|
||||
fs.writeFile(filePath, dataObj, (err) => {
|
||||
if (err) throw err;
|
||||
console.log('Log is saved!');
|
||||
cb && cb();
|
||||
});
|
||||
return filePath;
|
||||
}
|
||||
|
||||
testUrls.forEach((link) => {
|
||||
test(link);
|
||||
});
|
@ -1,42 +0,0 @@
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
const spawn = require('child_process').spawn;
|
||||
const Server = require('../server/server.js');
|
||||
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
|
||||
describe('Test request with big body', () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000;
|
||||
printLog('Start server for no_rule_big_response');
|
||||
|
||||
serverInstance = new Server();
|
||||
proxyServer = ProxyServerUtil.defaultProxyServer();
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Closed server for no_rule_spec');
|
||||
});
|
||||
|
||||
it('should successfully get file', (done) => {
|
||||
const isWin = /^win/.test(process.platform);
|
||||
if (isWin) {
|
||||
done();
|
||||
} else {
|
||||
const curl = spawn('curl', ['http://localhost:3000/big_response', '--proxy', 'http://127.0.0.1:8001', '-o', '/dev/null']);
|
||||
curl.on('close', (code) => {
|
||||
expect(code).toEqual(0);
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
@ -1,425 +0,0 @@
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Server = require('../server/server.js');
|
||||
const
|
||||
{
|
||||
proxyGet,
|
||||
proxyPost,
|
||||
directGet,
|
||||
directPost,
|
||||
directUpload,
|
||||
proxyUpload,
|
||||
generateUrl,
|
||||
proxyPut,
|
||||
directPut,
|
||||
proxyDelete,
|
||||
directDelete,
|
||||
directHead,
|
||||
proxyHead,
|
||||
directOptions,
|
||||
proxyOptions,
|
||||
proxyPutUpload,
|
||||
directPutUpload
|
||||
} = require('../util/HttpUtil.js');
|
||||
const { CommonRequestHeader } = require('../data/headers.js');
|
||||
const { isCommonResHeaderEqual, isCommonReqEqual, printLog } = require('../util/CommonUtil.js');
|
||||
const streamEqual = require('stream-equal');
|
||||
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
|
||||
testRequest('http');
|
||||
testRequest('https');
|
||||
|
||||
// Test suites for http and https request
|
||||
function testRequest(protocol = 'http') {
|
||||
function constructUrl(urlPath) {
|
||||
return generateUrl(protocol, urlPath);
|
||||
}
|
||||
|
||||
describe('Test request without proxy rules in protocol ' + protocol, () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000;
|
||||
printLog('Start server for no_rule_spec');
|
||||
|
||||
serverInstance = new Server();
|
||||
proxyServer = ProxyServerUtil.defaultProxyServer();
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Closed server for no_rule_spec');
|
||||
});
|
||||
|
||||
|
||||
it('Get should work as direct without proxy rules', (done) => {
|
||||
const url = constructUrl('/test');
|
||||
const getParam = {
|
||||
param: 'nothing'
|
||||
};
|
||||
|
||||
proxyGet(url, getParam, CommonRequestHeader).then((proxyRes) => {
|
||||
directGet(url, getParam, CommonRequestHeader).then(directRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error happend in direct get:', error);
|
||||
done.fail('error happend in direct get');
|
||||
});
|
||||
}, error => {
|
||||
console.log('error happened in proxy get:', error);
|
||||
done.fail('error happend in proxy get');
|
||||
});
|
||||
});
|
||||
|
||||
it('Post should work as direct without proxy rules', (done) => {
|
||||
const url = constructUrl('/test/getuser');
|
||||
const param = {
|
||||
param: 'postnothing'
|
||||
};
|
||||
|
||||
proxyPost(url, param, CommonRequestHeader).then(proxyRes => {
|
||||
directPost(url, param, CommonRequestHeader).then(directRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error in direct post:', error);
|
||||
done.fail('error happend in direct post');
|
||||
});
|
||||
}, error => {
|
||||
console.log('error happened in proxy post,', error);
|
||||
done.fail('error happend in proxy post');
|
||||
});
|
||||
});
|
||||
|
||||
it('PUT should work as direct without proxy rules', done => {
|
||||
const url = constructUrl('/test/put');
|
||||
const param = {
|
||||
param: 'putsomething'
|
||||
};
|
||||
proxyPut(url, param, CommonRequestHeader).then(proxyRes => {
|
||||
directPut(url, param, CommonRequestHeader).then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error happened in direct put', error);
|
||||
done.fail('error happened in direct put');
|
||||
});
|
||||
}, error => {
|
||||
console.error('error happened in proxy put', error);
|
||||
done.fail('error happened in proxy put');
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE rquest should work as direct without proxy rules', (done) => {
|
||||
const url = constructUrl('/test/delete/123456');
|
||||
|
||||
proxyDelete(url, {}, CommonRequestHeader).then(proxyRes => {
|
||||
directDelete(url, {}, CommonRequestHeader).then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
|
||||
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error happened in direct delete :', error);
|
||||
done.fail('error happened in direct delete');
|
||||
});
|
||||
}, error => {
|
||||
console.error('error happened in proxy delete :', error);
|
||||
done.fail('error happened in proxy delete');
|
||||
});
|
||||
});
|
||||
|
||||
it('HEAD request should work as direct without proxy rules', (done) => {
|
||||
const url = constructUrl('/test/head');
|
||||
|
||||
proxyHead(url, CommonRequestHeader)
|
||||
.then(proxyRes => {
|
||||
directHead(url, CommonRequestHeader)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body).toEqual('');
|
||||
|
||||
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error happened in direct head request:', error);
|
||||
done.fail('error happened in direct head request');
|
||||
});
|
||||
}, error => {
|
||||
console.error('error happened in proxy head request:', error);
|
||||
done.fail('error happened in proxy head request');
|
||||
});
|
||||
});
|
||||
|
||||
it('OPTIONS request should work as direct without proxy rules', (done) => {
|
||||
const url = constructUrl('/test/options');
|
||||
|
||||
proxyOptions(url, CommonRequestHeader)
|
||||
.then(proxyRes => {
|
||||
directOptions(url, CommonRequestHeader)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body).toEqual('could_be_empty');
|
||||
|
||||
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error happened in direct options request:', error);
|
||||
done.fail('error happened in direct options request');
|
||||
});
|
||||
}, error => {
|
||||
console.error('error happened in proxy options request:', error);
|
||||
done.fail('error happened in proxy options request');
|
||||
});
|
||||
});
|
||||
|
||||
it('304 should work as direct without proxy rules', (done) => {
|
||||
const url = constructUrl('/test/response/304');
|
||||
|
||||
proxyGet(url, CommonRequestHeader)
|
||||
.then(proxyRes => {
|
||||
directGet(url, CommonRequestHeader)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(304);
|
||||
expect(directRes.body).toEqual('');
|
||||
|
||||
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error happened in direct 304 request:', error);
|
||||
done.fail('error happened in direct 304 request');
|
||||
});
|
||||
}, error => {
|
||||
console.error('error happened in proxy 304 request:', error);
|
||||
done.fail('error happened in proxy 304 request');
|
||||
});
|
||||
})
|
||||
|
||||
describe('Response code should be honored as direct without proxy rules', () => {
|
||||
[301, 302, 303].forEach(code => {
|
||||
testRedirect(code);
|
||||
});
|
||||
|
||||
function testRedirect(redirectCode) {
|
||||
it(`${redirectCode} response should work as direct without proxy rules`, (done) => {
|
||||
const url = constructUrl(`/test/response/${redirectCode}`);
|
||||
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(redirectCode);
|
||||
expect(directRes.headers.location).toEqual(proxyRes.headers.location);
|
||||
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||
expect(directRes.headers.location).toEqual('/test');
|
||||
done();
|
||||
}).catch(error => {
|
||||
console.log(`error happened in direct ${redirectCode}:`, error);
|
||||
done.fail(`error happened in direct ${redirectCode}`);
|
||||
});
|
||||
}).catch(error => {
|
||||
console.log(`error happened in proxy ${redirectCode}:`, error);
|
||||
done.fail(`error happened in proxy ${redirectCode}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Test file download ', () => {
|
||||
const testArray = [
|
||||
{
|
||||
url: constructUrl('/test/download/png'),
|
||||
type: 'png',
|
||||
contentType: 'image/png'
|
||||
},
|
||||
{
|
||||
url: constructUrl('/test/download/webp'),
|
||||
type: 'WEBP',
|
||||
contentType: 'image/webp'
|
||||
},
|
||||
{
|
||||
url: constructUrl('/test/download/json'),
|
||||
type: 'JSON',
|
||||
contentType: 'application/json; charset=utf-8'
|
||||
},
|
||||
{
|
||||
url: constructUrl('/test/download/css'),
|
||||
type: 'CSS',
|
||||
contentType: 'text/css; charset=utf-8'
|
||||
},
|
||||
{
|
||||
url: constructUrl('/test/download/ttf'),
|
||||
type: 'TTF',
|
||||
contentType: 'application/x-font-ttf'
|
||||
},
|
||||
{
|
||||
url: constructUrl('/test/download/eot'),
|
||||
type: 'EOT',
|
||||
contentType: 'application/vnd.ms-fontobject'
|
||||
},
|
||||
{
|
||||
url: constructUrl('/test/download/svg'),
|
||||
type: 'SVG',
|
||||
contentType: 'image/svg+xml'
|
||||
},
|
||||
{
|
||||
url: constructUrl('/test/download/woff'),
|
||||
type: 'WOFF',
|
||||
contentType: 'application/font-woff'
|
||||
},
|
||||
{
|
||||
url: constructUrl('/test/download/woff2'),
|
||||
type: 'WOFF2',
|
||||
contentType: 'application/font-woff2'
|
||||
}
|
||||
];
|
||||
|
||||
testArray.forEach(item => {
|
||||
testFileDownload(item.url, item.type, item.contentType);
|
||||
});
|
||||
|
||||
// 封装测试文件下载的测试工具类
|
||||
function testFileDownload(url, filetype, contentType) {
|
||||
const describe = `${filetype} file download without rules should be work as direct download`;
|
||||
const param = {};
|
||||
|
||||
it(describe, (done) => {
|
||||
proxyGet(url, param).then(proxyRes => {
|
||||
directGet(url, param).then(directRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||
expect(proxyRes.body).toEqual(directRes.body);
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error in direct get :', filetype, error);
|
||||
done.fail(`error happend in direct get ${filetype}`);
|
||||
});
|
||||
}, error => {
|
||||
console.error('error in proxy get :', filetype, error);
|
||||
done.fail(`error happend in proxy get ${filetype}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Test file upload', () => {
|
||||
const formParams = {
|
||||
param1: 'param_1',
|
||||
param2: 'param2'
|
||||
};
|
||||
it('POST upload should be working', (done) => {
|
||||
const url = constructUrl('/test/upload/png');
|
||||
const filePath = path.resolve(__dirname, '../data/test.png');
|
||||
|
||||
proxyUpload(url, filePath, formParams)
|
||||
.then(proxyRes => {
|
||||
directUpload(url, filePath, formParams)
|
||||
.then((directRes) => {
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||
assertReponse(proxyRes, directRes, filePath, done);
|
||||
}, error => {
|
||||
console.error('error in direct upload:', error);
|
||||
done.fail('error in direct upload');
|
||||
});
|
||||
}, error => {
|
||||
console.error('error in proxy upload:', error);
|
||||
done.fail('error in proxy upload:');
|
||||
});
|
||||
});
|
||||
|
||||
it('PUT upload should be working', (done) => {
|
||||
const url = constructUrl('/test/upload/putpng');
|
||||
const filePath = path.resolve(__dirname, '../data/test.png');
|
||||
proxyPutUpload(url, filePath, formParams)
|
||||
.then(proxyRes => {
|
||||
directPutUpload(url, filePath, formParams)
|
||||
.then((directRes) => {
|
||||
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||
|
||||
assertReponse(proxyRes, directRes, filePath, done);
|
||||
}, error => {
|
||||
console.error('error in direct upload:', error);
|
||||
done.fail('error in direct upload');
|
||||
});
|
||||
}, error => {
|
||||
console.error('error in proxy upload:', error);
|
||||
done.fail('error in proxy upload:');
|
||||
});
|
||||
});
|
||||
|
||||
function assertReponse(proxyRes, directRes, originFilePath, done) {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
|
||||
expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||
// expect(proxyRes.headers.reqbody).toEqual(directRes.headers.reqbody);
|
||||
|
||||
// the body will be the file path
|
||||
const directUploadedStream = fs.createReadStream(directRes.body);
|
||||
const proxyUploadedStream = fs.createReadStream(proxyRes.body);
|
||||
const localFileStream = fs.createReadStream(originFilePath);
|
||||
streamEqual(directUploadedStream, localFileStream)
|
||||
.then(isLocalEqual => {
|
||||
expect(isLocalEqual).toBe(true);
|
||||
streamEqual(directUploadedStream, proxyUploadedStream)
|
||||
.then(isUploadedEqual => {
|
||||
expect(isUploadedEqual).toBe(true);
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error in comparing directUpload with proxy:\n', error);
|
||||
done.fail('error in comparing directUpload with proxy');
|
||||
});
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error in comparing directUpload with local:\n', error);
|
||||
done.fail('error in comparing directUpload with local');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Test suites for WebSocket.
|
||||
* ONLY TO ENSURE THE REQUEST WILL BE BYPASSED SUCCESSFULLY, WE HAVEN'T SUPPORTTED WEBSOCKET YET.
|
||||
*
|
||||
*/
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { generateWsUrl, directWs, proxyWs } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog, isArrayEqual } = require('../util/CommonUtil.js');
|
||||
|
||||
testWebsocket('ws');
|
||||
testWebsocket('wss');
|
||||
testWebsocket('ws', true);
|
||||
testWebsocket('wss', true);
|
||||
|
||||
function testWebsocket(protocol, masked = false) {
|
||||
describe('Test WebSocket in protocol : ' + protocol, () => {
|
||||
const url = generateWsUrl(protocol, '/test/socket');
|
||||
let serverInstance;
|
||||
let proxyServer;
|
||||
// the message to
|
||||
const testMessageArray = [
|
||||
'Send the message with default option1',
|
||||
'Send the message with default option2',
|
||||
'Send the message with default option3',
|
||||
'Send the message with default option4'
|
||||
];
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000;
|
||||
printLog('Start server for no_rule_websocket_spec');
|
||||
serverInstance = new Server();
|
||||
|
||||
proxyServer = ProxyServerUtil.defaultProxyServer();
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Closed server for no_rule_websocket_spec');
|
||||
});
|
||||
|
||||
it('Default websocket option', done => {
|
||||
const directMessages = []; // set the flag for direct message, compare when both direct and proxy got message
|
||||
const proxyMessages = [];
|
||||
let directHeaders;
|
||||
let proxyHeaders;
|
||||
|
||||
const ws = directWs(url);
|
||||
const proxyWsRef = proxyWs(url);
|
||||
ws.on('open', () => {
|
||||
ws.send(testMessageArray[0], masked);
|
||||
for (let i = 1; i < testMessageArray.length; i++) {
|
||||
setTimeout(() => {
|
||||
ws.send(testMessageArray[i], masked);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
proxyWsRef.on('open', () => {
|
||||
try {
|
||||
proxyWsRef.send(testMessageArray[0], masked);
|
||||
for (let i = 1; i < testMessageArray.length; i++) {
|
||||
setTimeout(() => {
|
||||
proxyWsRef.send(testMessageArray[i], masked);
|
||||
}, 1000);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('headers', (headers) => {
|
||||
directHeaders = headers;
|
||||
compareMessageIfReady();
|
||||
});
|
||||
|
||||
proxyWsRef.on('headers', (headers) => {
|
||||
proxyHeaders = headers;
|
||||
compareMessageIfReady();
|
||||
});
|
||||
|
||||
ws.on('message', (data, flag) => {
|
||||
const message = JSON.parse(data);
|
||||
if (message.type === 'onMessage') {
|
||||
directMessages.push(message.content);
|
||||
compareMessageIfReady();
|
||||
}
|
||||
});
|
||||
|
||||
proxyWsRef.on('message', (data, flag) => {
|
||||
const message = JSON.parse(data);
|
||||
if (message.type === 'onMessage') {
|
||||
proxyMessages.push(message.content);
|
||||
compareMessageIfReady();
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', error => {
|
||||
console.error('error happened in direct websocket:', error);
|
||||
done.fail('Error happened in direct websocket');
|
||||
});
|
||||
|
||||
proxyWsRef.on('error', error => {
|
||||
console.error('error happened in proxy websocket:', error);
|
||||
done.fail('Error happened in proxy websocket');
|
||||
});
|
||||
|
||||
function compareMessageIfReady() {
|
||||
const targetLen = testMessageArray.length;
|
||||
if (directMessages.length === targetLen
|
||||
&& proxyMessages.length === targetLen
|
||||
&& directHeaders && proxyHeaders
|
||||
) {
|
||||
expect(isArrayEqual(directMessages, testMessageArray)).toBe(true);
|
||||
expect(isArrayEqual(directMessages, proxyMessages)).toBe(true);
|
||||
expect(directHeaders['x-anyproxy-websocket']).toBeUndefined();
|
||||
expect(proxyHeaders['x-anyproxy-websocket']).toBe('true');
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/*
|
||||
* add authToken parameter to the request data
|
||||
*
|
||||
*/
|
||||
module.exports = {
|
||||
*beforeSendRequest(requestDetail) {
|
||||
if (requestDetail.url.indexOf('/getuser') >= 0) {
|
||||
let requestStr = requestDetail.requestData.toString();
|
||||
try {
|
||||
requestStr = JSON.stringify(Object.assign(JSON.parse(requestStr), {
|
||||
authToken: 'auth_token_inrule'
|
||||
}))
|
||||
} catch (e) {
|
||||
requestStr += '&authToken=auth_token_inrule';
|
||||
}
|
||||
return {
|
||||
requestOptions: requestDetail.requestOptions,
|
||||
requestData: requestStr
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
@ -1,13 +0,0 @@
|
||||
//rule scheme :
|
||||
module.exports = {
|
||||
|
||||
*beforeSendRequest(requestDetail) {
|
||||
const newOption = requestDetail.requestOptions;
|
||||
if (newOption.hostname === 'localhost' && newOption.path === '/test/should_replace_option') {
|
||||
newOption.path = '/test/new_replace_option';
|
||||
return {
|
||||
requestOptions: newOption
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
//rule scheme :
|
||||
module.exports = {
|
||||
*summary() {
|
||||
return 'The rule to replace request protocol';
|
||||
},
|
||||
|
||||
*beforeSendRequest(requestDetail) {
|
||||
const newConfig = {
|
||||
protocol: 'http',
|
||||
requestOptions: requestDetail.requestOptions
|
||||
};
|
||||
newConfig.requestOptions.port = 3000;
|
||||
return newConfig;
|
||||
}
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
//rule scheme : replace the reponse data
|
||||
|
||||
module.exports = {
|
||||
|
||||
*beforeSendResponse(requestDetail, responseDetail) {
|
||||
if (requestDetail.url.indexOf('/test/normal_request1') > -1) {
|
||||
const newResponse = responseDetail.response;
|
||||
|
||||
const newDataStr = newResponse.body.toString() + '_hello_world!';
|
||||
newResponse.body = newDataStr;
|
||||
|
||||
return {
|
||||
response: newResponse
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
//rule scheme : remove the cache headers in response headers
|
||||
module.exports = {
|
||||
*summary() {
|
||||
return 'The rule to remove the cache headers in response';
|
||||
},
|
||||
|
||||
*beforeSendResponse(requestDetail, responseDetail) {
|
||||
if (requestDetail.url.indexOf('/test/normal_request1') >= 0) {
|
||||
const newResponse = responseDetail.response;
|
||||
newResponse.header.replacedheaderkey = 'replacedHeader_value_in_rule';
|
||||
|
||||
return {
|
||||
response: newResponse
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// replaceResponseHeader(req, res, header) {
|
||||
// const d = Q.defer();
|
||||
|
||||
// header = Object.assign({}, header);
|
||||
// if (req.url.indexOf('test/normal_request1') > -1) {
|
||||
// header.replacedheaderkey = 'replacedHeader_value_in_rule';
|
||||
// }
|
||||
|
||||
// d.resolve(header);
|
||||
|
||||
// return d.promise;
|
||||
// }
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
//replace all the images with local one
|
||||
module.exports = {
|
||||
|
||||
*summary() {
|
||||
return 'replace the response status code.';
|
||||
},
|
||||
|
||||
*beforeSendResponse(requestDetail, responseDetail) {
|
||||
if (requestDetail.url.indexOf('/test/normal_request1') >= 0) {
|
||||
const newResponse = responseDetail.response;
|
||||
newResponse.statusCode = 302;
|
||||
newResponse.header.location = 'www.taobao.com';
|
||||
|
||||
return {
|
||||
response: newResponse
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -1,19 +0,0 @@
|
||||
//rule scheme :
|
||||
|
||||
module.exports = {
|
||||
*summary() {
|
||||
return 'Rule to intercept https request';
|
||||
},
|
||||
|
||||
*beforeSendResponse(requestDetail, responseDetail) {
|
||||
const newResponse = responseDetail.response;
|
||||
newResponse.body = newResponse.body.toString() + '_hello_world';
|
||||
return {
|
||||
response: newResponse
|
||||
};
|
||||
},
|
||||
|
||||
*beforeDealHttpsRequest(requestDetail) {
|
||||
return requestDetail.host.indexOf('localhost:3001') > -1;
|
||||
}
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Rule defination for shouldUseLocalResponse
|
||||
*
|
||||
*/
|
||||
const localData = 'handled_in_local_response';
|
||||
|
||||
module.exports = {
|
||||
*beforeSendRequest(requestDetail) {
|
||||
if (requestDetail.url.indexOf('uselocal') > -1) {
|
||||
return {
|
||||
response: {
|
||||
statusCode: 200,
|
||||
header: {
|
||||
'Via-Proxy-Local': 'true'
|
||||
},
|
||||
body: localData
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
@ -1,154 +0,0 @@
|
||||
/*
|
||||
* test for rule replaceResponseStatus rule
|
||||
*
|
||||
*/
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyGet } = require('../util/HttpUtil.js');
|
||||
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
|
||||
let errorInRule = null;
|
||||
const ruleNotDealError = {
|
||||
*onError(requestDetail, error) {
|
||||
errorInRule = error;
|
||||
}
|
||||
};
|
||||
|
||||
let errorInConnect = null;
|
||||
const ruleDealConnectError = {
|
||||
*onConnectError(requestDetail, error) {
|
||||
errorInConnect = error;
|
||||
}
|
||||
};
|
||||
|
||||
const ERROR_PAGE_IN_RULE = 'this is my error page';
|
||||
const ruleReturnAnErrorPage = {
|
||||
*onError(requestDetail, error) {
|
||||
return {
|
||||
response: {
|
||||
statusCode: '200',
|
||||
header: {},
|
||||
body: ERROR_PAGE_IN_RULE,
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
testWrapper('http');
|
||||
testWrapper('https');
|
||||
testHttpsConnect();
|
||||
|
||||
function testWrapper(protocol) {
|
||||
describe('Rule should get an error in :' + protocol, () => {
|
||||
let proxyServer;
|
||||
// let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_deal_error_spec');
|
||||
errorInRule = null;
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(ruleNotDealError);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_deal_error_spec');
|
||||
});
|
||||
|
||||
it('Should get a request error', done => {
|
||||
const url = protocol + '://not_exist_url.anyproxy.io';
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(500);
|
||||
expect(proxyRes.headers['proxy-error']).toEqual('true');
|
||||
expect(errorInRule).not.toBe(null);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rule should return a custom error page in :' + protocol, () => {
|
||||
let proxyServer;
|
||||
// let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_deal_error_custom_error_page');
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(ruleReturnAnErrorPage);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_deal_error_custom_error_page');
|
||||
});
|
||||
|
||||
it('Should get a request error', done => {
|
||||
const url = protocol + '://not_exist_url.anyproxy.io';
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.headers['proxy-error']).toBe(undefined);
|
||||
expect(proxyRes.body).toEqual(ERROR_PAGE_IN_RULE);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testHttpsConnect() {
|
||||
describe('Rule should get a connect error', () => {
|
||||
let proxyServer;
|
||||
// let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
errorInConnect = null;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_deal_error_custom_error_page');
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(ruleDealConnectError, {
|
||||
forceProxyHttps: false
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_deal_error_custom_error_page');
|
||||
});
|
||||
|
||||
it('Should get a request error', done => {
|
||||
const url = 'https://not_exist_url.anyproxy.io';
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
done.fail('should throw an error when requesting');
|
||||
})
|
||||
.catch(error => {
|
||||
expect(errorInConnect).not.toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* test for rule replaceRequestData rule
|
||||
*
|
||||
*/
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyPost, generateUrl, directPost, isViaProxy } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog, isObjectEqual } = require('../util/CommonUtil.js');
|
||||
|
||||
const rule = require('./rule/rule_replace_request_data.js');
|
||||
|
||||
testWrapper('http');
|
||||
testWrapper('https');
|
||||
|
||||
function testWrapper(protocol) {
|
||||
describe('Rule replaceRequestData should be working in :' + protocol, () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_replace_request_data_spec');
|
||||
|
||||
serverInstance = new Server();
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(rule);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_replace_request_data_spec');
|
||||
});
|
||||
|
||||
it('Should replace the request data in proxy if the assertion is true', done => {
|
||||
const url = generateUrl(protocol, '/test/getuser');
|
||||
const userName = 'username_test';
|
||||
const param = {
|
||||
username: userName
|
||||
};
|
||||
|
||||
proxyPost(url, param)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.body).toEqual('body_post_getuser');
|
||||
const proxyRequest = serverInstance.getProxyRequestRecord(url);
|
||||
const proxyReqBodyObj = JSON.parse(proxyRequest.body.toString());
|
||||
|
||||
expect(isViaProxy(proxyRequest)).toBe(true);
|
||||
|
||||
expect(proxyReqBodyObj.username).toEqual(userName);
|
||||
expect(proxyReqBodyObj.authToken).toEqual('auth_token_inrule');
|
||||
|
||||
directPost(url, param)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
|
||||
|
||||
const directRequest = serverInstance.getRequestRecord(url);
|
||||
const directReqBodyObj = JSON.parse(directRequest.body.toString());
|
||||
expect(isViaProxy(directRequest)).toBe(false);
|
||||
|
||||
expect(directReqBodyObj.username).toEqual(userName);
|
||||
expect(directReqBodyObj.authToken).toBeUndefined();
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct post: ', error);
|
||||
done.fail('error happened in direct post');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy post: ', error);
|
||||
console.error(error);
|
||||
console.error(error.stack);
|
||||
done.fail('error happened in proxy post');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not replace the request data in proxy if the assertion is false', done => {
|
||||
const url = generateUrl(protocol, '/test/normal_post_request1');
|
||||
const userName = 'normal_username_test';
|
||||
|
||||
const param = {
|
||||
username: userName
|
||||
};
|
||||
|
||||
proxyPost(url, param)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.body).toEqual('body_normal_post_request1');
|
||||
const proxyReqRecord = serverInstance.getProxyRequestRecord(url);
|
||||
const proxyReqBody = JSON.parse(proxyReqRecord.body);
|
||||
expect(isObjectEqual(proxyReqBody, param, url)).toBe(true);
|
||||
|
||||
directPost(url, param)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
const directReqRecord = serverInstance.getRequestRecord(url);
|
||||
const directReqBody = JSON.parse(directReqRecord.body);
|
||||
expect(isObjectEqual(directReqBody, proxyReqBody, url)).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct post:', error);
|
||||
done.fail('error happened in direct post');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy post:', error);
|
||||
done.fail('error happened in proxy post');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* test for rule replaceOption rule
|
||||
*
|
||||
*/
|
||||
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyGet, generateUrl, directGet } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
|
||||
const rule = require('./rule/rule_replace_request_option.js');
|
||||
|
||||
testWrapper('http');
|
||||
testWrapper('https');
|
||||
|
||||
function testWrapper(protocol) {
|
||||
describe('Rule replaceRequestOption should be working in :' + protocol, () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll(done => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_replace_request_option_spec');
|
||||
|
||||
serverInstance = new Server();
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(rule);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_replace_request_option_spec');
|
||||
});
|
||||
|
||||
it('Should replace request option if the assertion is true', done => {
|
||||
const url = generateUrl(protocol, '/test/should_replace_option');
|
||||
const replacedUrl = generateUrl(protocol, '/test/new_replace_option');
|
||||
|
||||
const token = 'replacedOption' + Date.now();
|
||||
const directToken = 'notRepacedOption' + Date.now();
|
||||
proxyGet(url, {}, { token })
|
||||
.then(proxyRes => {
|
||||
directGet(url, {}, { token: directToken })
|
||||
.then(directRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.body).toEqual('the_new_replaced_option_page_content');
|
||||
|
||||
const proxyRequestObj = serverInstance.getProxyRequestRecord(replacedUrl);
|
||||
expect(proxyRequestObj.headers.token).toEqual(token);
|
||||
expect(proxyRequestObj.headers['via-proxy']).toEqual('true');
|
||||
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body).toEqual('the_request_that_has_not_be_replaced');
|
||||
|
||||
const directRequestObj = serverInstance.getRequestRecord(url);
|
||||
expect(directRequestObj.headers.token).toEqual(directToken);
|
||||
|
||||
done();
|
||||
}).catch(error => {
|
||||
console.error('error happened in direct get for replaceOption rule: ', error);
|
||||
done.fail('error happened when direct test replaceOption rule ');
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('error happened in proxy get for replaceOption rule: ', error);
|
||||
done.fail('error happened when proxy test replaceOption rule ');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not replace request option if the assertion is false', done => {
|
||||
const url = generateUrl(protocol, '/test/should_not_replace_option');
|
||||
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
done();
|
||||
}, error => {
|
||||
console.error('error happened in proxy get:', error);
|
||||
done.fail('error happened in proxy get');
|
||||
}).catch(error => {
|
||||
console.error('error happend in syntax:', error);
|
||||
done.fail('error happend in syntax');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
/*
|
||||
* test for rule replaceOption rule
|
||||
*
|
||||
*/
|
||||
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyGet, generateUrl, directGet } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
|
||||
const rule = require('./rule/rule_replace_request_protocol.js');
|
||||
|
||||
testWrapper();
|
||||
|
||||
function testWrapper() {
|
||||
describe('Rule replaceRequestProtocol should be working', () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_replace_request_protocol');
|
||||
|
||||
serverInstance = new Server();
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(rule);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_replace_request_protocol');
|
||||
});
|
||||
|
||||
it('Should replace request protocol in PROXY https request', done => {
|
||||
const url = generateUrl('https', '/test/normal_request1');
|
||||
const httpUrl = url.replace('https', 'http');
|
||||
const token = 'proxy_request1_token_' + Date.now();
|
||||
proxyGet(url, {}, { token })
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.body).toEqual('body_normal_request1');
|
||||
|
||||
// there should be no https url be requested in proxy, it should be http request
|
||||
expect(serverInstance.getProxyRequestRecord(url)).toBe(null);
|
||||
const httpRecord = serverInstance.getProxyRequestRecord(httpUrl);
|
||||
expect(httpRecord.headers.token).toEqual(token);
|
||||
expect(httpRecord.headers['via-proxy']).toEqual('true');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error happened in proxy the https request: ', error);
|
||||
done.fail('error happened in proxy the https request');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not replace protocol in PROXY http request', done => {
|
||||
const url = generateUrl('http', '/test/normal_request2');
|
||||
const token = 'proxy_request2_token_' + Date.now();
|
||||
proxyGet(url, {}, { token })
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.body).toEqual('body_normal_request2');
|
||||
const requestRecord = serverInstance.getProxyRequestRecord(url);
|
||||
expect(requestRecord).not.toBe(null);
|
||||
expect(requestRecord.headers.token).toEqual(token);
|
||||
expect(requestRecord.headers['via-proxy']).toEqual('true');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy the http request: ', error);
|
||||
done.fail('error happend in proxy the http request');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should the direct request still be working with https', done => {
|
||||
const url = generateUrl('https', '/test/normal_request1');
|
||||
const token = 'direct_request1_token_' + Date.now();
|
||||
directGet(url, {}, { token })
|
||||
.then(directRes => {
|
||||
expect(directRes.body).toEqual('body_normal_request1');
|
||||
const requestRecord = serverInstance.getRequestRecord(url);
|
||||
expect(requestRecord.headers.token).toEqual(token);
|
||||
expect(requestRecord.headers['via-proxy']).toBeUndefined();
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct https get:', error);
|
||||
done.fail('error happened in direct https get');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should the direct request still be working with http', done => {
|
||||
const url = generateUrl('http', '/test/normal_request2');
|
||||
const token = 'direct_request1_token_' + Date.now();
|
||||
directGet(url, {}, { token })
|
||||
.then(directRes => {
|
||||
expect(directRes.body).toEqual('body_normal_request2');
|
||||
const requestRecord = serverInstance.getRequestRecord(url);
|
||||
expect(requestRecord.headers.token).toEqual(token);
|
||||
expect(requestRecord.headers['via-proxy']).toBeUndefined();
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct http get:', error);
|
||||
done.fail('error happened in direct http get');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* test for rule replaceResponseData rule
|
||||
*
|
||||
*/
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyGet, generateUrl, directGet } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
|
||||
const rule = require('./rule/rule_replace_response_data.js');
|
||||
|
||||
testWrapper('http');
|
||||
testWrapper('https');
|
||||
|
||||
function testWrapper(protocol) {
|
||||
describe('Rule replaceResponseData should be working in :' + protocol, () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_replace_response_data');
|
||||
|
||||
serverInstance = new Server();
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(rule);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_replace_response_data');
|
||||
});
|
||||
|
||||
it('Should replace the header in proxy if assertion is true', done => {
|
||||
const url = generateUrl(protocol, '/test/normal_request1');
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.body).toEqual('body_normal_request1_hello_world!');
|
||||
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body).toEqual('body_normal_request1');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct get: ', error);
|
||||
done.fail('error happened in direct get');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not replace the header in proxy if assertion is false', done => {
|
||||
const url = generateUrl(protocol, '/test/normal_request2');
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.body).toEqual('body_normal_request2');
|
||||
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct get: ', error);
|
||||
done.fail('error happened in direct get');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* test for rule replaceResponseHeader rule
|
||||
*
|
||||
*/
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyGet, generateUrl, directGet } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
|
||||
const rule = require('./rule/rule_replace_response_header.js');
|
||||
|
||||
testWrapper('http');
|
||||
testWrapper('https');
|
||||
|
||||
function testWrapper(protocol) {
|
||||
describe('Rule replaceResponseHeader should be working in :' + protocol, () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_replace_response_header_spec');
|
||||
|
||||
serverInstance = new Server();
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(rule);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_replace_response_header_spec');
|
||||
});
|
||||
|
||||
it('Should replace the header in proxy if assertion is true', done => {
|
||||
const url = generateUrl(protocol, '/test/normal_request1');
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.headers.replacedheaderkey).toEqual('replacedHeader_value_in_rule');
|
||||
expect(proxyRes.body).toEqual('body_normal_request1');
|
||||
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.headers.replacedheaderkey).toBeUndefined();
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct get: ', error);
|
||||
done.fail('error happened in direct get');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not replace the header in proxy if the assertion is false', done => {
|
||||
const url = generateUrl(protocol, '/test/normal_request2');
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.headers.replacedheaderkey).toBeUndefined();
|
||||
expect(proxyRes.body).toEqual('body_normal_request2');
|
||||
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.headers.replacedheaderkey).toBeUndefined();
|
||||
expect(directRes.body).toEqual(proxyRes.body);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct get: ', error);
|
||||
done.fail('error happened in direct get');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* test for rule replaceResponseStatus rule
|
||||
*
|
||||
*/
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyGet, generateUrl, directGet } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
|
||||
const rule = require('./rule/rule_replace_response_status_code.js');
|
||||
|
||||
testWrapper('http');
|
||||
testWrapper('https');
|
||||
|
||||
function testWrapper(protocol) {
|
||||
describe('Rule replaceResponseStatus should be working in :' + protocol, () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_replace_response_status_code');
|
||||
|
||||
serverInstance = new Server();
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(rule);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_replace_response_status_code');
|
||||
});
|
||||
|
||||
it('Should replace the status code in proxy if assertion is true', done => {
|
||||
const url = generateUrl(protocol, '/test/normal_request1');
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(302);
|
||||
expect(proxyRes.headers.location).toEqual('www.taobao.com');
|
||||
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body).toEqual('body_normal_request1');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct get: ', error);
|
||||
done.fail('error happened in direct get');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not replace the status code in proxy if assertion is false', done => {
|
||||
const url = generateUrl(protocol, '/test/normal_request2');
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
expect(proxyRes.body).toEqual('body_normal_request2');
|
||||
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body).toEqual('body_normal_request2');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct get: ', error);
|
||||
done.fail('error happened in direct get');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* test for rule shouldInterceptHttpsReq rule
|
||||
*
|
||||
*/
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyGet, directGet } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
|
||||
const rule = require('./rule/rule_should_intercept_https_req.js');
|
||||
|
||||
testWrapper();
|
||||
|
||||
function testWrapper() {
|
||||
describe('Rule shouldInterceptHttpsReq should be working', () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll(done => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for test_rule_should_intercept_https_req_spec');
|
||||
|
||||
serverInstance = new Server();
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(rule, {
|
||||
forceProxyHttps: false
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for test_rule_should_intercept_https_req_spec');
|
||||
});
|
||||
|
||||
it('Should replace the header in proxy if assertion is true', done => {
|
||||
const url = 'https://localhost:3001/test';
|
||||
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body + '_hello_world').toEqual(proxyRes.body);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct get: ', error);
|
||||
done.fail('error happened in direct get');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not replace the header in proxy if assertion is false', done => {
|
||||
const url = 'https://localhost:3002/test';
|
||||
proxyGet(url)
|
||||
.then(proxyRes => {
|
||||
expect(proxyRes.statusCode).toEqual(200);
|
||||
|
||||
directGet(url)
|
||||
.then(directRes => {
|
||||
expect(directRes.statusCode).toEqual(200);
|
||||
expect(directRes.body.replace(/\s/g, '')).toEqual(proxyRes.body.replace(/\s/g, ''));
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in direct get: ', error);
|
||||
done.fail('error happened in direct get');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error happened in proxy get: ', error);
|
||||
done.fail('error happened in proxy get');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* test for rule shouldUseLocal
|
||||
*
|
||||
*/
|
||||
|
||||
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
|
||||
const { proxyGet, generateUrl } = require('../util/HttpUtil.js');
|
||||
const Server = require('../server/server.js');
|
||||
const { printLog } = require('../util/CommonUtil.js');
|
||||
|
||||
const rule = require('./rule/rule_should_use_local_response.js');
|
||||
|
||||
const expectedLocalBody = 'handled_in_local_response';
|
||||
|
||||
testWrapper('http');
|
||||
testWrapper('https');
|
||||
|
||||
function testWrapper(protocol) {
|
||||
describe('Rule shouldUseLocalResponse should be working in :' + protocol, () => {
|
||||
let proxyServer;
|
||||
let serverInstance;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
printLog('Start server for rule_shouldUseLocalResponse_spec');
|
||||
|
||||
serverInstance = new Server();
|
||||
|
||||
proxyServer = ProxyServerUtil.proxyServerWithRule(rule);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
serverInstance && serverInstance.close();
|
||||
proxyServer && proxyServer.close();
|
||||
printLog('Close server for rule_shouldUseLocalResponse_spec');
|
||||
});
|
||||
|
||||
it('Should use local response if the assertion is true', done => {
|
||||
const url = generateUrl(protocol, '/test/uselocal');
|
||||
proxyGet(url, {})
|
||||
.then(res => {
|
||||
expect(res.body).toEqual(expectedLocalBody);
|
||||
expect(res.headers['via-proxy-local']).toEqual('true');
|
||||
done();
|
||||
}).catch((error) => {
|
||||
console.log('error happened in proxy get for shouldUseLocal: ', error);
|
||||
done.fail('error happened when test shouldUseLocal rule');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
80
test/util.js
Normal file
80
test/util.js
Normal file
@ -0,0 +1,80 @@
|
||||
const request = require('request');
|
||||
const assert = require('assert');
|
||||
// TODO
|
||||
const { freshRequire, getFreePort } = require('../lib/util.js');
|
||||
|
||||
function basicProxyRequest(proxyHost, method, url, headers, qs, payload) {
|
||||
assert(method && url, 'method and url are required');
|
||||
assert(proxyHost, 'proxyHost is required');
|
||||
headers = Object.assign({
|
||||
'via-anyproxy': 'true',
|
||||
}, headers || {});
|
||||
|
||||
const requestOpt = {
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
followRedirect: false,
|
||||
rejectUnauthorized: false,
|
||||
qs,
|
||||
proxy: proxyHost,
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (error, response, body) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve({
|
||||
response,
|
||||
body,
|
||||
});
|
||||
}
|
||||
};
|
||||
if (payload) {
|
||||
payload.pipe(request(requestOpt, callback));
|
||||
} else {
|
||||
request(requestOpt, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
type: 'http',
|
||||
port: 8001,
|
||||
webInterface: false,
|
||||
wsIntercept: true,
|
||||
// throttle: 10000, // optional, speed limit in kb/s
|
||||
forceProxyHttps: true, // intercept https as well
|
||||
dangerouslyIgnoreUnauthorized: true,
|
||||
silent: false //optional, do not print anything into terminal. do not set it when you are still debugging.
|
||||
};
|
||||
|
||||
async function proxyServerWithRule(rule, overrideConfig) {
|
||||
const AnyProxy = freshRequire('../proxy.js');
|
||||
const freeportA = await getFreePort();
|
||||
const freeportB = await getFreePort();
|
||||
const options = Object.assign(DEFAULT_OPTIONS, {
|
||||
port: freeportA,
|
||||
webInterface: {
|
||||
enable: true,
|
||||
webPort: freeportB,
|
||||
}
|
||||
}, overrideConfig || {});
|
||||
options.rule = rule;
|
||||
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const instance = new AnyProxy.ProxyServer(options);
|
||||
instance.on('error', reject);
|
||||
instance.on('ready', () => {
|
||||
resolve(instance);
|
||||
});
|
||||
instance.start();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
basicProxyRequest,
|
||||
proxyServerWithRule,
|
||||
};
|
@ -1,269 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* The utility class for test
|
||||
*/
|
||||
const color = require('colorful');
|
||||
|
||||
function _isDeepEqual(source, target) {
|
||||
// if the objects are Array
|
||||
if (source.constructor === Array && target.constructor === Array) {
|
||||
if (source.length !== target.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let _isEqual = true;
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
if (!_isDeepEqual(source[i], target[i])) {
|
||||
_isEqual = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _isEqual;
|
||||
}
|
||||
|
||||
// if the source and target are just object
|
||||
if (typeof source === 'object' && typeof target === 'object') {
|
||||
let _isEqual = true;
|
||||
for (const key in source) {
|
||||
if (!_isDeepEqual(source[key], target[key])) {
|
||||
_isEqual = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _isEqual;
|
||||
}
|
||||
|
||||
return source === target;
|
||||
}
|
||||
/*
|
||||
* Compare whether tow object are equal
|
||||
*/
|
||||
function isObjectEqual(source = {}, target = {}, url = '') {
|
||||
source = Object.assign({}, source);
|
||||
target = Object.assign({}, target);
|
||||
let isEqual = true;
|
||||
|
||||
for (const key in source) {
|
||||
isEqual = isEqual && _isDeepEqual(source[key], target[key]);
|
||||
|
||||
if (!isEqual) {
|
||||
console.info('source object :', source);
|
||||
console.info('target object :', target);
|
||||
printError(`different key in isObjectEqual is: "${key}", source is "${source[key]}",
|
||||
target is "${target[key]}" the url is ${url}`);
|
||||
break;
|
||||
}
|
||||
|
||||
delete source[key];
|
||||
delete target[key];
|
||||
}
|
||||
|
||||
for (const key in target) {
|
||||
isEqual = isEqual && source[key] === target[key];
|
||||
|
||||
if (!isEqual) {
|
||||
console.info('source object :', source);
|
||||
console.info('target object :', target);
|
||||
printError(`different key in isObjectEqual is: "${key}", source is "${source[key]}",
|
||||
target is "${target[key]}" the url is ${url}`);
|
||||
break;
|
||||
}
|
||||
|
||||
delete source[key];
|
||||
delete target[key];
|
||||
}
|
||||
|
||||
return isEqual;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare the header between direct with proxy
|
||||
* Will exclude the header(s) which modified by proxy
|
||||
*/
|
||||
function isCommonResHeaderEqual(directHeaders, proxyHeaders, requestUrl) {
|
||||
directHeaders = Object.assign({}, directHeaders);
|
||||
proxyHeaders = Object.assign({}, proxyHeaders);
|
||||
let isEqual = true;
|
||||
const mustEqualFileds = []; // the fileds that have to be equal, or the assert will be failed
|
||||
|
||||
if (!/gzip/i.test(directHeaders['content-encoding'])) {
|
||||
// if the content is gzipped, proxy will unzip and remove the header
|
||||
mustEqualFileds.push('content-encoding');
|
||||
}
|
||||
mustEqualFileds.push('content-type');
|
||||
mustEqualFileds.push('cache-control');
|
||||
mustEqualFileds.push('allow');
|
||||
|
||||
// ensure the required fileds are same
|
||||
mustEqualFileds.forEach(filedName => {
|
||||
isEqual = directHeaders[filedName] === proxyHeaders[filedName];
|
||||
delete directHeaders[filedName];
|
||||
delete proxyHeaders[filedName];
|
||||
});
|
||||
|
||||
// remained filed are good to be same, but are allowed to be different
|
||||
// will warn out those different fileds
|
||||
for (const key in directHeaders) {
|
||||
if (!_isDeepEqual(directHeaders[key], proxyHeaders[key])) {
|
||||
printWarn(`key "${key}" of two response headers are different in request "${requestUrl}" :
|
||||
direct is: "${directHeaders[key]}", proxy is: "${proxyHeaders[key]}"`);
|
||||
}
|
||||
}
|
||||
|
||||
return isEqual;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare the request between direct with proxy
|
||||
*
|
||||
*/
|
||||
function isCommonReqEqual(url, serverInstance) {
|
||||
try {
|
||||
let isEqual = true;
|
||||
|
||||
const directReqObj = serverInstance.getRequestRecord(url);
|
||||
const proxyReqObj = serverInstance.getProxyRequestRecord(url);
|
||||
|
||||
// ensure the proxy header is correct
|
||||
isEqual = isEqual && proxyReqObj.headers['via-proxy'] === 'true';
|
||||
delete proxyReqObj.headers['via-proxy'];
|
||||
|
||||
directReqObj.headers['content-type'] = trimFormContentType(directReqObj.headers['content-type']);
|
||||
proxyReqObj.headers['content-type'] = trimFormContentType(proxyReqObj.headers['content-type']);
|
||||
|
||||
// avoid compare content-length header via proxy
|
||||
delete directReqObj.headers['content-length'];
|
||||
delete proxyReqObj.headers['content-length'];
|
||||
delete directReqObj.headers['transfer-encoding'];
|
||||
delete proxyReqObj.headers['transfer-encoding'];
|
||||
|
||||
isEqual = isEqual && directReqObj.url === proxyReqObj.url;
|
||||
isEqual = isEqual && isObjectEqual(directReqObj.headers, proxyReqObj.headers, url);
|
||||
isEqual = isEqual && directReqObj.body === proxyReqObj.body;
|
||||
return isEqual;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* for multipart-form, the boundary will be different with each update, we trim it here
|
||||
*/
|
||||
function trimFormContentType(contentType = '') {
|
||||
return contentType.replace(/boundary.*/, '');
|
||||
}
|
||||
|
||||
|
||||
function printLog(content) {
|
||||
console.log(color.blue('==LOG==: ' + content));
|
||||
}
|
||||
|
||||
function printWarn(content) {
|
||||
console.log(color.magenta('==WARN==: ' + content));
|
||||
}
|
||||
|
||||
function printError(content) {
|
||||
console.log(color.red('==ERROR==: ' + content));
|
||||
}
|
||||
|
||||
function printHilite(content) {
|
||||
console.log(color.yellow('==LOG==: ' + content));
|
||||
}
|
||||
|
||||
function parseUrlQuery(string = '') {
|
||||
const parameterArray = string.split('&');
|
||||
const parsedObj = {};
|
||||
parameterArray.forEach((parameter) => {
|
||||
// 获取等号的位置
|
||||
const indexOfEqual = parameter.indexOf('=');
|
||||
const name = parameter.substr(0, indexOfEqual);
|
||||
const value = parameter.substr(indexOfEqual + 1);
|
||||
parsedObj[name] = value;
|
||||
});
|
||||
return parsedObj;
|
||||
}
|
||||
|
||||
function stringSimilarity(a, b, precision = 2) {
|
||||
let similarity = '0%';
|
||||
let isCongruent = false;
|
||||
if (a && b) {
|
||||
const targetLen = Math.max(a.length, b.length);
|
||||
targetLen > 1000 ?
|
||||
similarity = simHasH(a, b) :
|
||||
similarity = LevenshteinSimilarity(a, b);
|
||||
isCongruent = similarity === 100;
|
||||
similarity = similarity.toFixed(precision) + '%';
|
||||
}
|
||||
return {
|
||||
isCongruent,
|
||||
similarity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* simhash similarity
|
||||
*/
|
||||
function simHasH(a, b) {
|
||||
const simhash = require('node-simhash');
|
||||
return (simhash.compare(a, b) * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Levenshtein Distance
|
||||
*/
|
||||
function LevenshteinSimilarity(a, b) {
|
||||
let cost;
|
||||
const maxLen = Math.max(a.length, b.length);
|
||||
const minOfThree = (numa, numb, numc) => {
|
||||
if (numa > numb) {
|
||||
return numb > numc ? numc : numb;
|
||||
} else {
|
||||
return numa > numc ? numc : numa;
|
||||
}
|
||||
}
|
||||
if (a.length === 0) cost = b.length;
|
||||
if (b.length === 0) cost = a.length;
|
||||
|
||||
if (a.length > b.length) {
|
||||
const tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
|
||||
const row = [];
|
||||
for (let i = 0; i <= a.length; i++) {
|
||||
row[i] = i;
|
||||
}
|
||||
|
||||
for (let i = 1; i <= b.length; i++) {
|
||||
let prev = i;
|
||||
for (let j = 1; j <= a.length; j++) {
|
||||
let val;
|
||||
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||
val = row[j - 1];
|
||||
} else {
|
||||
val = minOfThree(row[j - 1] + 1, prev + 1, row[j] + 1);
|
||||
}
|
||||
row[j - 1] = prev;
|
||||
prev = val;
|
||||
}
|
||||
row[a.length] = prev;
|
||||
}
|
||||
cost = row[a.length];
|
||||
return ((maxLen - cost) / maxLen * 100);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isObjectEqual,
|
||||
isCommonResHeaderEqual,
|
||||
printLog,
|
||||
printWarn,
|
||||
printError,
|
||||
printHilite,
|
||||
isCommonReqEqual,
|
||||
parseUrlQuery,
|
||||
stringSimilarity,
|
||||
isArrayEqual: _isDeepEqual
|
||||
};
|
@ -1,352 +0,0 @@
|
||||
/* eslint prefer-arrow-callback: 0 */
|
||||
/**
|
||||
* An util to make the request out
|
||||
*
|
||||
*/
|
||||
const request = require('request');
|
||||
const fs = require('fs');
|
||||
const WebSocket = require('ws');
|
||||
const HttpsProxyAgent = require('https-proxy-agent');
|
||||
const stream = require('stream');
|
||||
|
||||
const PROXY_HOST = 'http://localhost:8001';
|
||||
const SOCKET_PROXY_HOST = 'http://localhost:8001';
|
||||
|
||||
|
||||
const HTTP_SERVER_BASE = 'http://localhost:3000';
|
||||
const HTTPS_SERVER_BASE = 'https://localhost:3001';
|
||||
const WS_SERVER_BASE = 'ws://localhost:3000';
|
||||
const WSS_SERVER_BASE = 'wss://localhost:3001';
|
||||
const DEFAULT_CHUNK_COLLECT_THRESHOLD = 20 * 1024 * 1024; // about 20 mb
|
||||
|
||||
class commonStream extends stream.Readable {
|
||||
constructor(config) {
|
||||
super({
|
||||
highWaterMark: DEFAULT_CHUNK_COLLECT_THRESHOLD * 5
|
||||
});
|
||||
}
|
||||
_read(size) {}
|
||||
}
|
||||
|
||||
function getHostFromUrl(url = '') {
|
||||
const hostReg = /^(https{0,1}:\/\/)(\w+)/;
|
||||
const match = url.match(hostReg);
|
||||
|
||||
return match && match[2] ? match[2] : '';
|
||||
}
|
||||
|
||||
function getPortFromUrl(url = '') {
|
||||
const portReg = /^https{0,1}:\/\/\w+(:(\d+)){0,1}/;
|
||||
const match = url.match(portReg);
|
||||
let port = match && match[2] ? match[2] : '';
|
||||
|
||||
if (!port) {
|
||||
port = url.indexOf('https://') === 0 ? 443 : 80;
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取url中的path
|
||||
*/
|
||||
function getPathFromUrl(url = '') {
|
||||
const pathReg = /^https{0,1}:\/\/\w+(:\d+){0,1}(.+)/;
|
||||
const match = url.match(pathReg);
|
||||
const path = match && match[3] ? match[2] : url;
|
||||
return path;
|
||||
}
|
||||
|
||||
function proxyRequest(method = 'GET', url, params, headers = {}) {
|
||||
return doRequest(method, url, params, headers, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* 直接请求到真实服务器,不经过代理服务器
|
||||
*
|
||||
*/
|
||||
function directRequest(method = 'GET', url, params, headers = {}) {
|
||||
return doRequest(method, url, params, headers);
|
||||
}
|
||||
|
||||
function directUpload(url, filepath, formParams = {}, headers = {}) {
|
||||
return doUpload(url, 'POST', filepath, formParams, headers);
|
||||
}
|
||||
|
||||
function proxyUpload(url, filepath, formParams = {}, headers = {}) {
|
||||
return doUpload(url, 'POST', filepath, formParams, headers, true);
|
||||
}
|
||||
|
||||
function directPutUpload(url, filepath, formParams = {}, headers = {}) {
|
||||
return doUpload(url, 'PUT', filepath, formParams, headers);
|
||||
}
|
||||
|
||||
function proxyPutUpload(url, filepath, headers = {}) {
|
||||
return doUpload(url, 'PUT', filepath, headers, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param params {String} json类型或file路径
|
||||
* {Object} key-value形式
|
||||
*/
|
||||
function doRequest(method = 'GET', url, params, headers = {}, isProxy) {
|
||||
headers = Object.assign({}, headers);
|
||||
|
||||
let reqStream = new commonStream();
|
||||
const requestData = {
|
||||
headers,
|
||||
followRedirect: false,
|
||||
rejectUnauthorized: false
|
||||
};
|
||||
|
||||
if (isProxy) {
|
||||
requestData.proxy = PROXY_HOST;
|
||||
requestData.headers['via-proxy'] = 'true';
|
||||
}
|
||||
|
||||
const streamReq = (resolve, reject) => {
|
||||
requestData.headers['content-type'] = 'text/plain'; //otherwise, koa-body could not recognize
|
||||
if (typeof params === 'string') {
|
||||
fs.existsSync(params) ?
|
||||
reqStream = fs.createReadStream(params) :
|
||||
reqStream.push(params);
|
||||
} else if (typeof params === 'object') {
|
||||
reqStream.push(JSON.stringify(params));
|
||||
}
|
||||
reqStream.push(null);
|
||||
reqStream.pipe(request[method.toLowerCase()](
|
||||
url,
|
||||
requestData,
|
||||
(error, response, body) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
const commonReq = (resolve, reject) => {
|
||||
requestData.url = url;
|
||||
requestData.method = method;
|
||||
requestData.qs = params;
|
||||
request(
|
||||
requestData,
|
||||
(error, response, body) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
const requestTask = new Promise((resolve, reject) => {
|
||||
if (method === 'POST' || method === 'PUT') {
|
||||
streamReq(resolve, reject);
|
||||
} else {
|
||||
commonReq(resolve, reject);
|
||||
}
|
||||
});
|
||||
return requestTask;
|
||||
}
|
||||
|
||||
|
||||
function doUpload(url, method, filepath, formParams, headers = {}, isProxy) {
|
||||
let formData = {
|
||||
file: fs.createReadStream(filepath)
|
||||
};
|
||||
|
||||
formData = Object.assign({}, formData, formParams);
|
||||
headers = Object.assign({}, headers);
|
||||
|
||||
const requestData = {
|
||||
formData,
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
json: true,
|
||||
rejectUnauthorized: false
|
||||
};
|
||||
|
||||
if (isProxy) {
|
||||
requestData.proxy = PROXY_HOST;
|
||||
requestData.headers['via-proxy'] = 'true';
|
||||
}
|
||||
const requestTask = new Promise((resolve, reject) => {
|
||||
request(
|
||||
requestData,
|
||||
(error, response, body) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
return requestTask;
|
||||
}
|
||||
|
||||
function doWebSocket(url, isProxy) {
|
||||
let ws;
|
||||
if (isProxy) {
|
||||
const agent = new HttpsProxyAgent(SOCKET_PROXY_HOST);
|
||||
ws = new WebSocket(url, {
|
||||
agent,
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
} else {
|
||||
ws = new WebSocket(url, {
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
}
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
function proxyGet(url, params, headers = {}) {
|
||||
return proxyRequest('GET', url, params, headers);
|
||||
}
|
||||
|
||||
function proxyPost(url, params, headers = {}) {
|
||||
return proxyRequest('POST', url, params, headers);
|
||||
}
|
||||
|
||||
function proxyPut(url, params, headers = {}) {
|
||||
return proxyRequest('PUT', url, params, headers);
|
||||
}
|
||||
|
||||
function proxyDelete(url, params, headers = {}) {
|
||||
return proxyRequest('DELETE', url, params, headers);
|
||||
}
|
||||
|
||||
function proxyHead(url, headers = {}) {
|
||||
return proxyRequest('HEAD', url, {}, headers);
|
||||
}
|
||||
|
||||
function proxyOptions(url, headers = {}) {
|
||||
return proxyRequest('OPTIONS', url, {}, headers);
|
||||
}
|
||||
|
||||
function directGet(url, params, headers = {}) {
|
||||
return directRequest('GET', url, params, headers);
|
||||
}
|
||||
|
||||
function directPost(url, params, headers = {}) {
|
||||
return directRequest('POST', url, params, headers);
|
||||
}
|
||||
|
||||
function directPut(url, params, headers = {}) {
|
||||
return directRequest('PUT', url, params, headers);
|
||||
}
|
||||
|
||||
function directDelete(url, params, headers = {}) {
|
||||
return directRequest('DELETE', url, params, headers);
|
||||
}
|
||||
|
||||
function directHead(url, headers = {}) {
|
||||
return directRequest('HEAD', url, {}, headers);
|
||||
}
|
||||
|
||||
function directOptions(url, headers = {}) {
|
||||
return directRequest('OPTIONS', url, {}, headers);
|
||||
}
|
||||
|
||||
function proxyWs(url) {
|
||||
return doWebSocket(url, true);
|
||||
}
|
||||
|
||||
function directWs(url) {
|
||||
return doWebSocket(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* generate the final url based on protocol and path
|
||||
*
|
||||
*/
|
||||
function generateUrl(protocol, urlPath) {
|
||||
return protocol === 'http' ? HTTP_SERVER_BASE + urlPath : HTTPS_SERVER_BASE + urlPath;
|
||||
}
|
||||
|
||||
function generateWsUrl(protocol, urlPath) {
|
||||
return protocol === 'wss' ? WSS_SERVER_BASE + urlPath : WS_SERVER_BASE + urlPath;
|
||||
}
|
||||
|
||||
/*
|
||||
* verify if the request data is a valid proxy request, by checking specified header
|
||||
*/
|
||||
function isViaProxy(req) {
|
||||
return req.headers['via-proxy'] === 'true';
|
||||
}
|
||||
|
||||
/*
|
||||
* check if url is supported by request moudle
|
||||
*/
|
||||
function isSupportedProtocol(requestPath) {
|
||||
return requestPath.indexOf('http://') === 0 || requestPath.indexOf('https://') === 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* collect all request data in one url
|
||||
*/
|
||||
function getRequestListFromPage(pageUrl, cb) {
|
||||
let _ph;
|
||||
let _page;
|
||||
let _outObj;
|
||||
const phantom = require('phantom');
|
||||
console.log(`collecting requests from ${pageUrl}...`);
|
||||
return phantom.create().then(ph => {
|
||||
_ph = ph;
|
||||
return _ph.createPage();
|
||||
}).then(page => {
|
||||
_page = page;
|
||||
_outObj = _ph.createOutObject();
|
||||
_outObj.urls = [];
|
||||
page.property('onResourceRequested', function (requestData, networkRequest, out) {
|
||||
out.urls.push(requestData);
|
||||
}, _outObj);
|
||||
return _page.open(pageUrl);
|
||||
})
|
||||
.then(status => _outObj.property('urls'))
|
||||
.then(urls => {
|
||||
_page.close();
|
||||
_ph.exit();
|
||||
return urls;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`failed to collecting requests from ${pageUrl}`);
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
getHostFromUrl,
|
||||
getPathFromUrl,
|
||||
getPortFromUrl,
|
||||
proxyGet,
|
||||
proxyPost,
|
||||
directGet,
|
||||
directPost,
|
||||
directUpload,
|
||||
proxyUpload,
|
||||
generateUrl,
|
||||
proxyWs,
|
||||
directWs,
|
||||
generateWsUrl,
|
||||
directPut,
|
||||
proxyPut,
|
||||
directDelete,
|
||||
proxyDelete,
|
||||
directHead,
|
||||
proxyHead,
|
||||
directOptions,
|
||||
proxyOptions,
|
||||
directPutUpload,
|
||||
proxyPutUpload,
|
||||
isViaProxy,
|
||||
getRequestListFromPage,
|
||||
directRequest,
|
||||
proxyRequest,
|
||||
isSupportedProtocol
|
||||
};
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Utility class for creating proxy server, used to create specfied proxy server
|
||||
*
|
||||
*/
|
||||
|
||||
const util = require('../../lib/util.js');
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
type: 'http',
|
||||
port: 8001,
|
||||
webInterface: {
|
||||
enable: true,
|
||||
webPort: 8002, // optional, port for web interface
|
||||
},
|
||||
wsIntercept: true,
|
||||
throttle: 10000, // optional, speed limit in kb/s
|
||||
forceProxyHttps: true, // intercept https as well
|
||||
dangerouslyIgnoreUnauthorized: true,
|
||||
silent: false //optional, do not print anything into terminal. do not set it when you are still debugging.
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @return An instance of proxy, could be closed by calling `instance.close()`
|
||||
*/
|
||||
function defaultProxyServer() {
|
||||
const AnyProxy = util.freshRequire('../proxy.js');
|
||||
|
||||
const options = util.merge({}, DEFAULT_OPTIONS);
|
||||
const instance = new AnyProxy.ProxyServer(options);
|
||||
instance.on('error', e => {
|
||||
console.log('server instance error', e);
|
||||
});
|
||||
instance.start();
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create proxy server with rule
|
||||
* @param rules
|
||||
Object, the rule object which contains required intercept method
|
||||
@return An instance of proxy, could be closed by calling `instance.close()`
|
||||
*/
|
||||
function proxyServerWithRule(rule, overrideConfig) {
|
||||
const AnyProxy = util.freshRequire('../proxy.js');
|
||||
|
||||
const options = Object.assign({}, DEFAULT_OPTIONS, overrideConfig);
|
||||
options.rule = rule;
|
||||
|
||||
const instance = new AnyProxy.ProxyServer(options);
|
||||
instance.on('error', e => {
|
||||
console.log('server instance error', e);
|
||||
});
|
||||
instance.start();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
function proxyServerWithoutHttpsIntercept(rule) {
|
||||
const AnyProxy = util.freshRequire('../proxy.js');
|
||||
|
||||
const options = util.merge({}, DEFAULT_OPTIONS);
|
||||
if (rule) {
|
||||
options.rule = rule;
|
||||
}
|
||||
options.forceProxyHttps = false;
|
||||
|
||||
const instance = new AnyProxy.ProxyServer(options);
|
||||
instance.on('error', e => {
|
||||
console.log('server instance error', e);
|
||||
});
|
||||
instance.start();
|
||||
return instance;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
defaultProxyServer,
|
||||
proxyServerWithoutHttpsIntercept,
|
||||
proxyServerWithRule
|
||||
};
|
50
test/web/curlUtil.spec.js
Normal file
50
test/web/curlUtil.spec.js
Normal file
@ -0,0 +1,50 @@
|
||||
const { curlify } = require('../../web/src/common/curlUtil');
|
||||
|
||||
describe('Test the curlify function', () => {
|
||||
it('request with headers', () => {
|
||||
const requestDetail = {
|
||||
method: 'POST',
|
||||
url: 'https://localhost:3001/test',
|
||||
reqHeader: {
|
||||
'via-proxy': 'true',
|
||||
},
|
||||
};
|
||||
const result = 'curl \'https://localhost:3001/test\' -X POST -H \'via-proxy: true\'';
|
||||
expect(curlify(requestDetail)).toBe(result);
|
||||
});
|
||||
|
||||
it('request with JSON body', () => {
|
||||
const requestDetail = {
|
||||
method: 'POST',
|
||||
url: 'https://localhost:3001/test',
|
||||
reqHeader: {
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
},
|
||||
reqBody: '{"action":1,"method":"test"}',
|
||||
};
|
||||
const result = `curl '${requestDetail.url}' -X POST -H 'content-type: application/json; charset=utf-8' -d '${requestDetail.reqBody}'`;
|
||||
expect(curlify(requestDetail)).toBe(result);
|
||||
});
|
||||
|
||||
it('accpet gzip encoding with compressed flag', () => {
|
||||
const requestDetail = {
|
||||
method: 'GET',
|
||||
url: 'https://localhost:3001/test',
|
||||
reqHeader: {
|
||||
Host: 'localhost',
|
||||
'Accept-Encoding': 'gzip',
|
||||
},
|
||||
};
|
||||
const result = 'curl \'https://localhost:3001/test\' -H \'Host: localhost\' -H \'Accept-Encoding: gzip\' --compressed';
|
||||
expect(curlify(requestDetail)).toBe(result);
|
||||
});
|
||||
|
||||
it('escape url character', () => {
|
||||
const requestDetail = {
|
||||
method: 'GET',
|
||||
url: 'https://localhost:3001/test?a[]=1',
|
||||
};
|
||||
const result = 'curl \'https://localhost:3001/test?a\\[\\]=1\'';
|
||||
expect(curlify(requestDetail)).toBe(result);
|
||||
});
|
||||
});
|
27
test/web/webInterface.spec.js
Normal file
27
test/web/webInterface.spec.js
Normal file
@ -0,0 +1,27 @@
|
||||
const WebInterface = require('../../lib/webInterface.js');
|
||||
const Recorder = require('../../lib/recorder');
|
||||
const urllib = require('urllib');
|
||||
|
||||
describe('WebInterface server', () => {
|
||||
let webServer = null;
|
||||
const webHost = 'http://127.0.0.1:8002'
|
||||
|
||||
beforeAll(async () => {
|
||||
const recorder = new Recorder();
|
||||
webServer = new WebInterface({
|
||||
webPort: 8002,
|
||||
}, recorder);
|
||||
await webServer.start();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await webServer.close();
|
||||
});
|
||||
|
||||
it('should response qrcode string in /getQrCode', async () => {
|
||||
const response = await urllib.request(`${webHost}/api/getQrCode`);
|
||||
const body = JSON.parse(response.res.data);
|
||||
expect(body.qrImgDom).toMatch('<img src="data:image/');
|
||||
expect(body.url).toBe(`${webHost}/downloadCrt`);
|
||||
});
|
||||
});
|
5
web/postcss.config.js
Normal file
5
web/postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* AJAX操作工具类
|
||||
*/
|
||||
import PromiseUtil from './PromiseUtil';
|
||||
import PromiseUtil from './promiseUtil';
|
||||
export function getJSON(url, data) {
|
||||
const d = PromiseUtil.defer();
|
||||
fetch(url + serializeQuery(data))
|
||||
@ -53,10 +53,10 @@ export function isApiSuccess (response) {
|
||||
return response.status === 'success';
|
||||
}
|
||||
|
||||
const ApiUtil = {
|
||||
const apiUtil = {
|
||||
getJSON,
|
||||
postJSON,
|
||||
isApiSuccess
|
||||
};
|
||||
|
||||
export default ApiUtil;
|
||||
export default apiUtil;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* define all Constant variables here
|
||||
* define all constant variables here
|
||||
*/
|
||||
|
||||
module.exports.MenuKeyMap = {
|
||||
|
@ -15,7 +15,7 @@ import { message } from 'antd';
|
||||
*/
|
||||
export function initWs(wsPort = location.port, path = 'do-not-proxy') {
|
||||
if(!WebSocket){
|
||||
throw (new Error('WebSocket is not supportted on this browser'));
|
||||
throw (new Error('WebSocket is not supported on this browser'));
|
||||
}
|
||||
|
||||
const wsClient = new WebSocket(`ws://${location.hostname}:${wsPort}/${path}`);
|
||||
@ -39,3 +39,4 @@ export function initWs(wsPort = location.port, path = 'do-not-proxy') {
|
||||
export default {
|
||||
initWs: initWs
|
||||
};
|
||||
|
||||
|
62
web/src/common/apiUtil.js
Normal file
62
web/src/common/apiUtil.js
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* AJAX操作工具类
|
||||
*/
|
||||
import PromiseUtil from './promiseUtil';
|
||||
export function getJSON(url, data) {
|
||||
const d = PromiseUtil.defer();
|
||||
fetch(url + serializeQuery(data))
|
||||
.then((data) => {
|
||||
d.resolve(data.json());
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
d.reject(error);
|
||||
});
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
export function postJSON(url, data) {
|
||||
const d = PromiseUtil.defer();
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then((data) => {
|
||||
|
||||
d.resolve(data.json());
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
d.reject(error);
|
||||
});
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
function serializeQuery (data = {}) {
|
||||
data['__t'] = Date.now();// disable the cache
|
||||
const queryArray = [];
|
||||
|
||||
for (let key in data) {
|
||||
queryArray.push(`${key}=${data[key]}`);
|
||||
}
|
||||
|
||||
const queryStr = queryArray.join('&');
|
||||
|
||||
return queryStr ? '?' + queryStr : '';
|
||||
}
|
||||
|
||||
export function isApiSuccess (response) {
|
||||
return response.status === 'success';
|
||||
}
|
||||
|
||||
const apiUtil = {
|
||||
getJSON,
|
||||
postJSON,
|
||||
isApiSuccess
|
||||
};
|
||||
|
||||
export default apiUtil;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user