Compare commits

...

70 Commits

Author SHA1 Message Date
xiaofeng.mxf
b93f948107 release 4.1.3 2020-06-18 23:37:09 +08:00
shadyzoz
8f3e54b1c5
fix: move cache to os.tmpdir() (#562)
* fix: move cache to os.tmpdir()

* revert snapshot
2020-06-18 23:25:45 +08:00
shadyzoz
78858bfb94
fix: replace upload fixture image.png with a smaller upload.txt (#563) 2020-06-17 20:58:32 +08:00
xiaofeng.mxf
5a1af37614 fix https proxy server for ip host 2020-01-23 11:02:23 +08:00
Otto Mao
28108d4c78
introduce jest and travis-ci (#541)
introduce jest and travis-ci
2020-01-21 10:59:14 +08:00
xiaofeng.mxf
d3c306b976 release v4.1.2 2020-01-01 19:49:11 +08:00
xiaofeng.mxf
3e2daef4aa Merge branch 'silentcloud-fix/wss-port' 2020-01-01 19:36:17 +08:00
silentcloud
e003c887fa fix: default wss port will cause service close 2020-01-01 19:36:05 +08:00
xiaofeng.mxf
68188c9cd6 fix #419 #291 #258 #206 2020-01-01 19:32:30 +08:00
xiaofeng.mxf
8951fb68ef update eslint config 2020-01-01 16:45:58 +08:00
xiaofeng.mxf
f653718dc4 release v4.1.1 2020-01-01 16:24:48 +08:00
xiaofeng.mxf
9e7eb6c7ad Merge branch 'nestoralonso-patch-3' 2020-01-01 16:22:50 +08:00
xiaofeng.mxf
8ca35ba809 Merge branch 'patch-3' of git://github.com/nestoralonso/anyproxy into nestoralonso-patch-3 2020-01-01 16:22:43 +08:00
xiaofeng.mxf
c9dea0cffd Merge branch 'nestoralonso-patch-1' 2020-01-01 16:21:42 +08:00
xiaofeng.mxf
a3ebfdd164 Merge branch 'patch-1' of git://github.com/nestoralonso/anyproxy into nestoralonso-patch-1 2020-01-01 16:21:35 +08:00
xiaofeng.mxf
faf8b03a67 Merge branch 'nestoralonso-patch-2' 2020-01-01 16:21:10 +08:00
xiaofeng.mxf
54fa80ad81 Merge branch 'patch-2' of git://github.com/nestoralonso/anyproxy into nestoralonso-patch-2 2020-01-01 16:20:17 +08:00
xiaofeng.mxf
d9f2e7528c fix #508 2020-01-01 16:18:54 +08:00
nestoralonso
bef9474885
Update record-detail.jsx
Remove unused variables and imports
2019-10-07 09:46:38 -05:00
nestoralonso
a6ab1a5ce8
Update rootSaga.js
Fix typo in message
2019-10-07 09:41:31 -05:00
nestoralonso
0927d98222
Update WsUtil.js
Fix typo in message
2019-10-07 09:34:51 -05:00
xiaofeng.mxf
65f530047a release v4.1.0 2019-03-26 21:01:48 +08:00
xiaofeng.mxf
1c3184b044 Merge branch 'guox191-download_cer' 2019-03-26 21:00:27 +08:00
xiaofeng.mxf
2f63cd96c1 update document 2019-03-26 20:56:25 +08:00
xiaofeng.mxf
01e0b01949 Merge branch 'download_cer' of git://github.com/guox191/anyproxy into guox191-download_cer 2019-03-26 20:40:33 +08:00
guox191
7f02664079 add CA download page 2019-03-26 14:47:37 +08:00
guox191
15d7ed48bf docs tip 2019-03-25 23:53:47 +08:00
guox191
67151c071b support select CA file extension 2019-03-25 22:58:44 +08:00
xiaofeng.mxf
ac16f87ce6 Merge branch 'guox191-master' 2019-03-25 14:01:07 +08:00
xiaofeng.mxf
f900904a44 Merge branch 'master' of git://github.com/guox191/anyproxy into guox191-master 2019-03-25 14:00:59 +08:00
xiaofeng.mxf
344dcf8f66 Merge branch 'guox191-docs' 2019-03-25 13:28:14 +08:00
guox191
933cf25300 update docs: node.js support 2019-03-25 12:25:49 +08:00
xiaofeng.mxf
d9548ae7cf update version to 4.0.15 2019-03-24 21:33:47 +08:00
xiaofeng.mxf
fc4befa209 Merge branch 'fix/path#391' 2019-03-24 21:26:44 +08:00
xiaofeng.mxf
9d3e4776a2 Merge branch 'master' into fix/path#391 2019-03-24 21:26:34 +08:00
xiaofeng.mxf
5958e770f2 Merge branch 'wuchangming-master' 2019-03-24 21:18:33 +08:00
xiaofeng.mxf
db95da680d build document 2019-03-24 21:18:21 +08:00
xiaofeng.mxf
ed91608118 building docs 2019-03-24 21:16:32 +08:00
xiaofeng.mxf
cb25f1eff1 Merge branch 'master' of git://github.com/wuchangming/anyproxy into wuchangming-master 2019-03-24 21:16:01 +08:00
guox191
3a310b0c0e test deflate content 2019-03-23 14:56:02 +08:00
guox191
763bdc07a2 test curlify function 2019-03-22 10:01:31 +08:00
guox191
c040ae4578 add test cases for compressed response 2019-03-21 18:18:20 +08:00
yuanqin.wyq
9a9c076554 fix: prevent path crossing #391
fix: compatibility with node version
2019-03-18 22:22:29 +08:00
xiaofeng.mxf
a8c9f590fc Merge branch 'guox191-fix468' 2019-03-13 22:43:21 +08:00
guox191
59e05850fa fix #468 2019-03-13 21:26:40 +08:00
xiaofeng.mxf
d44ce7fd08 update tip for visiting homepage 2019-02-26 23:40:36 +08:00
xiaofeng.mxf
9682926e67 fix #427 2019-02-26 23:26:50 +08:00
Nick
ce1327205c
Merge pull request #414 from alibaba/module-case-senstive
fix issue 413,  fix case sensitive on certain platform
2018-08-09 16:46:14 +08:00
砚然
df6ab4baf7 fix issue 413, update the module name to fix case sensitive on certain platform 2018-08-09 16:43:30 +08:00
Otto Mao
0621dadf28
Merge pull request #401 from alibaba/ca_helper
Add ca helper when run anyproxy -i
2018-08-06 11:36:25 +08:00
砚然
e7732049db guide to the homepage when there are more info to visit 2018-07-11 15:20:39 +08:00
砚然
0241b90e4d add ca helper when run anyproxy -i
check and help to generate the root CA when run in https interception mode, and install the CA after user's confirmation (Mac only for now)
2018-07-03 21:14:00 +08:00
wuchangming
bf9b1cebe1 typo
大小写敏感的操作系统下,会导致模块找不到的错误。
2018-05-31 08:54:32 +08:00
砚然
71477d5aae add the missed dependency 2018-05-05 15:31:44 +08:00
砚然
2e9e556e26 update version to 4.0.9 2018-05-03 23:25:35 +08:00
Otto Mao
8937cc66a9
Merge pull request #363 from alibaba/remove-npm-dep
remove `npm` dependency from package.json
2018-05-03 13:03:09 +08:00
砚然
f271a8a248 remove npm dependency from package.json 2018-05-02 12:19:41 +08:00
Otto Mao
cd8a725c11
Merge pull request #362 from alibaba/remove-npm-dep
remove the unused dependency
2018-05-02 12:11:00 +08:00
砚然
cb428c9662 remove the unused dependency 2018-05-02 12:07:37 +08:00
Otto Mao
10f84d0f99
Merge pull request #351 from alibaba/ws-header-fix
pass the headers when proxy websocket request
2018-04-26 21:14:40 +08:00
砚然
aae5c9b039 pass through all the valid headers when proxing the WebSocket, and adds related test cases 2018-03-23 15:42:38 +08:00
Otto Mao
11e68100a4
Merge pull request #331 from alibaba/web-ui-enhance
Web ui enhance
2018-02-09 16:47:00 +08:00
砚然
e01bb38b80 uglify the output js, and udpate webpack to 3 2018-02-08 22:44:28 +08:00
砚然
1fd69b3e87 stringify the error object in header 2018-02-08 22:42:30 +08:00
Otto Mao
98fc3fa2ee building docs 2018-02-06 00:06:52 +08:00
Otto Mao
a5027340dc
Merge pull request #329 from alibaba/webinterfac-fix
fix the start issue when webinterface is disabled, andd add test cases
2018-02-06 00:05:58 +08:00
砚然
310f84d68f fix the start issue when webinterface is disabled, andd add test cases 2018-02-05 21:04:27 +08:00
Otto Mao
1405356292
Merge pull request #328 from alibaba/ws-docs
add docs about turning on websocket proxy in command
2018-02-05 11:08:49 +08:00
砚然
4244abe9e0 add docs about turning on websocket proxy in command 2018-02-05 11:06:14 +08:00
Otto Mao
4d0a8207ff update document and version 2018-02-05 10:33:31 +08:00
121 changed files with 2207 additions and 3805 deletions

View File

@ -1,6 +0,0 @@
{
"presets": [
"es2015",
"stage-0"
]
}

View File

@ -5,7 +5,7 @@
"browser": true, "browser": true,
"node": true, "node": true,
"es6": true, "es6": true,
"jasmine": true "jest": true
}, },
"globals": { "globals": {
"React": true, "React": true,

2
.gitignore vendored
View File

@ -35,3 +35,5 @@ test/report
doc_compiled.md doc_compiled.md
docs-md/cn/doc.md docs-md/cn/doc.md
docs-md/en/doc.md docs-md/en/doc.md
docs/gitbook/gitbook-plugin-livereload/
node_modules

5
.travis.yml Normal file
View File

@ -0,0 +1,5 @@
language: node_js
node_js:
- 12
before_script:
- node ./bin/anyproxy-ca -g

View File

@ -4,10 +4,11 @@ AnyProxy
[![NPM version][npm-image]][npm-url] [![NPM version][npm-image]][npm-url]
[![node version][node-image]][node-url] [![node version][node-image]][node-url]
[![npm download][download-image]][download-url] [![npm download][download-image]][download-url]
[![Build Status](https://travis-ci.org/alibaba/anyproxy.svg?branch=master)](https://travis-ci.org/alibaba/anyproxy)
[npm-image]: https://img.shields.io/npm/v/anyproxy.svg?style=flat-square [npm-image]: https://img.shields.io/npm/v/anyproxy.svg?style=flat-square
[npm-url]: https://npmjs.org/package/anyproxy [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/ [node-url]: http://nodejs.org/download/
[download-image]: https://img.shields.io/npm/dm/anyproxy.svg?style=flat-square [download-image]: https://img.shields.io/npm/dm/anyproxy.svg?style=flat-square
[download-url]: https://npmjs.org/package/anyproxy [download-url]: https://npmjs.org/package/anyproxy
@ -20,7 +21,7 @@ Issue: https://github.com/alibaba/anyproxy/issues
AnyProxy是一个基于NodeJS的可供插件配置的HTTP/HTTPS代理服务器。 AnyProxy是一个基于NodeJS的可供插件配置的HTTP/HTTPS代理服务器。
主页:[AnyProxy.io](http://anyproxy.io) 主页:[AnyProxy.io](http://anyproxy.io),访问可能需要稳定的国际网络环境
![](https://gw.alipayobjects.com/zos/rmsportal/gUfcjGxLONndTfllxynC.jpg@_90q) ![](https://gw.alipayobjects.com/zos/rmsportal/gUfcjGxLONndTfllxynC.jpg@_90q)

11
babel.config.js Normal file
View File

@ -0,0 +1,11 @@
if (process.env.NODE_ENV === 'test') {
module.exports = {};
} else {
module.exports = {
presets: [
'es2015',
'stage-0'
]
};
}

View File

@ -4,9 +4,11 @@
const program = require('commander'), const program = require('commander'),
color = require('colorful'), color = require('colorful'),
co = require('co'),
packageInfo = require('../package.json'), packageInfo = require('../package.json'),
ruleLoader = require('../lib/ruleLoader'),
util = require('../lib/util'), util = require('../lib/util'),
rootCACheck = require('./rootCACheck'),
startServer = require('./startServer'),
logUtil = require('../lib/log'); logUtil = require('../lib/log');
program program
@ -24,7 +26,7 @@ program
if (program.clear) { if (program.clear) {
require('../lib/certMgr').clearCerts(() => { require('../lib/certMgr').clearCerts(() => {
util.deleteFolderContentsRecursive(util.getAnyProxyPath('cache')); util.deleteFolderContentsRecursive(util.getAnyProxyTmpPath());
console.log(color.green('done !')); console.log(color.green('done !'));
process.exit(0); process.exit(0);
}); });
@ -33,85 +35,20 @@ if (program.clear) {
process.exit(0); process.exit(0);
}); });
} else { } else {
const AnyProxy = require('../proxy.js'); co(function *() {
let proxyServer;
if (program.silent) { if (program.silent) {
logUtil.setPrintStatus(false); logUtil.setPrintStatus(false);
} }
// load rule module if (program.intercept) {
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 { try {
proxyServer && proxyServer.close(); yield rootCACheck();
} catch (e) { } catch (e) {
console.error(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); return startServer(program);
try { })
proxyServer && proxyServer.close();
} catch (e) {}
process.exit();
});
} }

View File

@ -20,10 +20,10 @@ program
.parse(process.argv); .parse(process.argv);
function openFolderOfFile(filePath) { function openFolderOfFile(filePath) {
const isWin = /^win/.test(process.platform); const platform = process.platform;
if (isWin) { if (/^win/.test(platform)) {
exec('start .', { cwd: path.dirname(filePath) }); exec('start .', { cwd: path.dirname(filePath) });
} else { } else if (/darwin/.test(platform)) {
exec(`open -R ${filePath}`); exec(`open -R ${filePath}`);
} }
} }
@ -33,7 +33,6 @@ function guideToGenrateCA() {
if (!error) { if (!error) {
const certDir = path.dirname(keyPath); const certDir = path.dirname(keyPath);
console.log(`The cert is generated at ${certDir}. Please trust the ${color.bold('rootCA.crt')}.`); console.log(`The cert is generated at ${certDir}. Please trust the ${color.bold('rootCA.crt')}.`);
// TODO: console.log('guide to install');
openFolderOfFile(crtPath); openFolderOfFile(crtPath);
} else { } else {
console.error('failed to generate rootCA', error); console.error('failed to generate rootCA', error);
@ -44,7 +43,6 @@ function guideToGenrateCA() {
function guideToTrustCA() { function guideToTrustCA() {
const certPath = AnyProxy.utils.certMgr.getRootCAFilePath(); const certPath = AnyProxy.utils.certMgr.getRootCAFilePath();
if (certPath) { if (certPath) {
// TODO: console.log('guide to install');
openFolderOfFile(certPath); openFolderOfFile(certPath);
} else { } else {
console.error('failed to get cert path'); console.error('failed to get cert path');

33
bin/rootCACheck.js Normal file
View 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
View 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();
});
}

View File

@ -9,6 +9,6 @@ node ./build_scripts/prebuild-doc.js
gitbook build ./docs-src ./docs gitbook build ./docs-src ./docs
## push the doc into github ## push the doc into github
git add ./docs # git add ./docs
git commit -m 'building docs' # git commit -m 'building docs'
git push origin # git push origin

View File

@ -79,11 +79,11 @@ const options = {
rule: require('myRuleModule'), rule: require('myRuleModule'),
webInterface: { webInterface: {
enable: true, enable: true,
webPort: 8002, webPort: 8002
wsPort: 8003,
}, },
throttle: 10000, throttle: 10000,
forceProxyHttps: false, forceProxyHttps: false,
wsIntercept: false, // 不开启websocket代理
silent: false silent: false
}; };
const proxyServer = new AnyProxy.ProxyServer(options); const proxyServer = new AnyProxy.ProxyServer(options);
@ -110,6 +110,7 @@ proxyServer.close();
* `forceProxyHttps` {boolean} 是否强制拦截所有的https忽略规则模块的返回默认`false` * `forceProxyHttps` {boolean} 是否强制拦截所有的https忽略规则模块的返回默认`false`
* `silent` {boolean} 是否屏蔽所有console输出默认`false` * `silent` {boolean} 是否屏蔽所有console输出默认`false`
* `dangerouslyIgnoreUnauthorized` {boolean} 是否忽略请求中的证书错误,默认`false` * `dangerouslyIgnoreUnauthorized` {boolean} 是否忽略请求中的证书错误,默认`false`
* `wsIntercept` {boolean} 是否开启websocket代理默认`false`
* `webInterface` {object} web版界面配置 * `webInterface` {object} web版界面配置
* `enable` {boolean} 是否启用web版界面默认`false` * `enable` {boolean} 是否启用web版界面默认`false`
* `webPort` {number} web版界面端口号默认`8002` * `webPort` {number} web版界面端口号默认`8002`
@ -163,7 +164,7 @@ proxyServer.close();
* 样例 * 样例
```js ```js
const AnyProxy = require('AnyProxy'); const AnyProxy = require('anyproxy');
const exec = require('child_process').exec; const exec = require('child_process').exec;
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
@ -199,6 +200,14 @@ anyproxy --intercept #启动AnyProxy并解析所有https请求
* [附录如何信任CA证书](#证书配置) * [附录如何信任CA证书](#证书配置)
# 代理WebSocket
```bash
anyproxy --ws-intercept
```
> 当启用`HTTPS`代理时,`wss`也会被代理但是不会被AnyProxy记录。需要开启`--ws-intercept`后才会从界面上看到相应内容。
# rule模块 # rule模块
AnyProxy提供了二次开发的能力你可以用js编写自己的规则模块rule来自定义网络请求的处理逻辑。 AnyProxy提供了二次开发的能力你可以用js编写自己的规则模块rule来自定义网络请求的处理逻辑。
@ -818,6 +827,8 @@ module.exports = {
* 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装 * 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装
* 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件进行安装 * 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件进行安装
不同安卓系统支持安装的证书文件类型不尽相同,大多支持安装拓展名为 .crt 的证书文件,少部分仅支持 .cer 文件(已知如 OPPO R15AnyProxy 提供了多种类型的证书文件,可在下载安装时选择。
### 配置iOS/Android系统代理 ### 配置iOS/Android系统代理
* 代理服务器都在wifi设置中配置 * 代理服务器都在wifi设置中配置

View File

@ -8,6 +8,7 @@
* [其他命令](README.md#其他命令) * [其他命令](README.md#其他命令)
* [作为npm模块启动](README.md#作为npm模块使用) * [作为npm模块启动](README.md#作为npm模块使用)
* [代理HTTPS](README.md#代理https) * [代理HTTPS](README.md#代理https)
* [代理WebSocket](README.md#代理websocket)
* [rule模块](README.md#rule模块) * [rule模块](README.md#rule模块)
* [开发示例](README.md#开发示例) * [开发示例](README.md#开发示例)
* [处理流程](README.md#处理流程) * [处理流程](README.md#处理流程)

View File

@ -164,7 +164,7 @@ proxyServer.close();
* 样例 * 样例
```js ```js
const AnyProxy = require('AnyProxy'); const AnyProxy = require('anyproxy');
const exec = require('child_process').exec; const exec = require('child_process').exec;
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
@ -200,6 +200,14 @@ anyproxy --intercept #启动AnyProxy并解析所有https请求
* [附录如何信任CA证书](#证书配置) * [附录如何信任CA证书](#证书配置)
# 代理WebSocket
```bash
anyproxy --ws-intercept
```
> 当启用`HTTPS`代理时,`wss`也会被代理但是不会被AnyProxy记录。需要开启`--ws-intercept`后才会从界面上看到相应内容。
# rule模块 # rule模块
AnyProxy提供了二次开发的能力你可以用js编写自己的规则模块rule来自定义网络请求的处理逻辑。 AnyProxy提供了二次开发的能力你可以用js编写自己的规则模块rule来自定义网络请求的处理逻辑。
@ -620,6 +628,8 @@ module.exports = {
* 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装 * 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装
* 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件进行安装 * 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件进行安装
不同安卓系统支持安装的证书文件类型不尽相同,大多支持安装拓展名为 .crt 的证书文件,少部分仅支持 .cer 文件(已知如 OPPO R15AnyProxy 提供了多种类型的证书文件,可在下载安装时选择。
### 配置iOS/Android系统代理 ### 配置iOS/Android系统代理
* 代理服务器都在wifi设置中配置 * 代理服务器都在wifi设置中配置

View File

@ -78,11 +78,11 @@ const options = {
rule: require('myRuleModule'), rule: require('myRuleModule'),
webInterface: { webInterface: {
enable: true, enable: true,
webPort: 8002, webPort: 8002
wsPort: 8003,
}, },
throttle: 10000, throttle: 10000,
forceProxyHttps: false, forceProxyHttps: false,
wsIntercept: false,
silent: false silent: false
}; };
const proxyServer = new AnyProxy.ProxyServer(options); const proxyServer = new AnyProxy.ProxyServer(options);
@ -106,11 +106,12 @@ proxyServer.close();
* `port` {number} required, port number of proxy server * `port` {number} required, port number of proxy server
* `rule` {object} your rule module * `rule` {object} your rule module
* `throttle` {number} throttle in kb/s, unlimited for default * `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` * `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 * `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 * `webPort` {number} port number for web interface
* Event: `ready` * Event: `ready`
* emit when proxy server is ready * emit when proxy server is ready
@ -162,7 +163,7 @@ proxyServer.close();
* Sample * Sample
```js ```js
const AnyProxy = require('AnyProxy'); const AnyProxy = require('anyproxy');
const exec = require('child_process').exec; const exec = require('child_process').exec;
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
@ -199,6 +200,13 @@ anyproxy --intercept #launch anyproxy and intercept all https traffic
* [Appendixhow to trust CA](#config-certification) * [Appendixhow 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 # 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. 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. * 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 ### 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. 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. 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 & 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 * 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 ### config iOS/Android proxy server

View File

@ -7,6 +7,7 @@
* [Options](README.md#options) * [Options](README.md#options)
* [As Node Module](README.md#use-anyproxy-as-an-npm-module) * [As Node Module](README.md#use-anyproxy-as-an-npm-module)
* [Proxy HTTPS](README.md#proxy-https) * [Proxy HTTPS](README.md#proxy-https)
* [Proxy WebSocket](README.md#proxy-websocket)
* [Rule Introduction](README.md#rule-introduction) * [Rule Introduction](README.md#rule-introduction)
* [Sample](README.md#sample) * [Sample](README.md#sample)
* [How Does It Work](README.md#how-does-it-work) * [How Does It Work](README.md#how-does-it-work)

View File

@ -163,7 +163,7 @@ proxyServer.close();
* Sample * Sample
```js ```js
const AnyProxy = require('AnyProxy'); const AnyProxy = require('anyproxy');
const exec = require('child_process').exec; const exec = require('child_process').exec;
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
@ -200,6 +200,13 @@ anyproxy --intercept #launch anyproxy and intercept all https traffic
* [Appendixhow to trust CA](#config-certification) * [Appendixhow 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 # 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. 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. * 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 ### 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. 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. 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 & 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 * 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 ### config iOS/Android proxy server

View File

@ -198,6 +198,22 @@
<li class="chapter " data-level="1.4" data-path="./"> <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模块"> <a href="./#rule模块">
@ -213,7 +229,7 @@
<ul class="articles"> <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="./#开发示例"> <a href="./#开发示例">
@ -229,7 +245,7 @@
</li> </li>
<li class="chapter " data-level="1.4.2" data-path="./"> <li class="chapter " data-level="1.5.2" data-path="./">
<a href="./#处理流程"> <a href="./#处理流程">
@ -245,7 +261,7 @@
</li> </li>
<li class="chapter " data-level="1.4.3" data-path="./"> <li class="chapter " data-level="1.5.3" data-path="./">
<a href="./#如何引用"> <a href="./#如何引用">
@ -266,7 +282,7 @@
</li> </li>
<li class="chapter " data-level="1.5" data-path="./"> <li class="chapter " data-level="1.6" data-path="./">
<a href="./#rule接口文档"> <a href="./#rule接口文档">
@ -283,7 +299,7 @@
<ul class="articles"> <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"> <a href="./#summary">
@ -299,7 +315,7 @@
</li> </li>
<li class="chapter " data-level="1.5.2" data-path="./"> <li class="chapter " data-level="1.6.2" data-path="./">
<a href="./#beforesendrequest"> <a href="./#beforesendrequest">
@ -315,7 +331,7 @@
</li> </li>
<li class="chapter " data-level="1.5.3" data-path="./"> <li class="chapter " data-level="1.6.3" data-path="./">
<a href="./#beforesendresponse"> <a href="./#beforesendresponse">
@ -331,7 +347,7 @@
</li> </li>
<li class="chapter " data-level="1.5.4" data-path="./"> <li class="chapter " data-level="1.6.4" data-path="./">
<a href="./#beforedealhttpsrequest"> <a href="./#beforedealhttpsrequest">
@ -347,7 +363,7 @@
</li> </li>
<li class="chapter " data-level="1.5.5" data-path="./"> <li class="chapter " data-level="1.6.5" data-path="./">
<a href="./#onerror"> <a href="./#onerror">
@ -363,7 +379,7 @@
</li> </li>
<li class="chapter " data-level="1.5.6" data-path="./"> <li class="chapter " data-level="1.6.6" data-path="./">
<a href="./#onconnecterror"> <a href="./#onconnecterror">
@ -384,7 +400,7 @@
</li> </li>
<li class="chapter " data-level="1.6" data-path="./"> <li class="chapter " data-level="1.7" data-path="./">
<a href="./#rule样例"> <a href="./#rule样例">
@ -401,7 +417,7 @@
<ul class="articles"> <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="./#使用本地数据"> <a href="./#使用本地数据">
@ -417,7 +433,7 @@
</li> </li>
<li class="chapter " data-level="1.6.2" data-path="./"> <li class="chapter " data-level="1.7.2" data-path="./">
<a href="./#修改请求头"> <a href="./#修改请求头">
@ -433,7 +449,7 @@
</li> </li>
<li class="chapter " data-level="1.6.3" data-path="./"> <li class="chapter " data-level="1.7.3" data-path="./">
<a href="./#修改请求数据"> <a href="./#修改请求数据">
@ -449,7 +465,7 @@
</li> </li>
<li class="chapter " data-level="1.6.4" data-path="./"> <li class="chapter " data-level="1.7.4" data-path="./">
<a href="./#修改请求的目标地址"> <a href="./#修改请求的目标地址">
@ -465,7 +481,7 @@
</li> </li>
<li class="chapter " data-level="1.6.5" data-path="./"> <li class="chapter " data-level="1.7.5" data-path="./">
<a href="./#修改请求协议"> <a href="./#修改请求协议">
@ -481,7 +497,7 @@
</li> </li>
<li class="chapter " data-level="1.6.6" data-path="./"> <li class="chapter " data-level="1.7.6" data-path="./">
<a href="./#修改返回状态码"> <a href="./#修改返回状态码">
@ -497,7 +513,7 @@
</li> </li>
<li class="chapter " data-level="1.6.7" data-path="./"> <li class="chapter " data-level="1.7.7" data-path="./">
<a href="./#修改返回头"> <a href="./#修改返回头">
@ -513,7 +529,7 @@
</li> </li>
<li class="chapter " data-level="1.6.8" data-path="./"> <li class="chapter " data-level="1.7.8" data-path="./">
<a href="./#修改返回内容并延迟"> <a href="./#修改返回内容并延迟">
@ -534,7 +550,7 @@
</li> </li>
<li class="chapter " data-level="1.7" data-path="./"> <li class="chapter " data-level="1.8" data-path="./">
<a href="./#证书配置"> <a href="./#证书配置">
@ -551,7 +567,7 @@
<ul class="articles"> <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证书"> <a href="./#osx系统信任ca证书">
@ -567,7 +583,7 @@
</li> </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证书"> <a href="./#windows系统信任ca证书">
@ -583,7 +599,7 @@
</li> </li>
<li class="chapter " data-level="1.7.3" data-path="./"> <li class="chapter " data-level="1.8.3" data-path="./">
<a href="./#配置osx系统代理"> <a href="./#配置osx系统代理">
@ -599,7 +615,7 @@
</li> </li>
<li class="chapter " data-level="1.7.4" data-path="./"> <li class="chapter " data-level="1.8.4" data-path="./">
<a href="./#配置浏览器http代理"> <a href="./#配置浏览器http代理">
@ -615,7 +631,7 @@
</li> </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证书"> <a href="./#ios系统信任ca证书">
@ -631,7 +647,7 @@
</li> </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证书"> <a href="./#ios--103信任ca证书">
@ -647,7 +663,7 @@
</li> </li>
<li class="chapter " data-level="1.7.7" data-path="./"> <li class="chapter " data-level="1.8.7" data-path="./">
<a href="./#安卓系统信任ca证书"> <a href="./#安卓系统信任ca证书">
@ -663,7 +679,7 @@
</li> </li>
<li class="chapter " data-level="1.7.8" data-path="./"> <li class="chapter " data-level="1.8.8" data-path="./">
<a href="./#配置iosandroid系统代理"> <a href="./#配置iosandroid系统代理">
@ -684,7 +700,7 @@
</li> </li>
<li class="chapter " data-level="1.8" data-path="./"> <li class="chapter " data-level="1.9" data-path="./">
<a href="./#faq"> <a href="./#faq">
@ -907,7 +923,7 @@ AnyProxy.utils.systemProxyMgr.disableGlobalProxy();
</li> </li>
<li>&#x6837;&#x4F8B;</li> <li>&#x6837;&#x4F8B;</li>
</ul> </ul>
<pre><code class="lang-js"> <span class="hljs-keyword">const</span> AnyProxy = <span class="hljs-built_in">require</span>(<span class="hljs-string">&apos;AnyProxy&apos;</span>); <pre><code class="lang-js"> <span class="hljs-keyword">const</span> AnyProxy = <span class="hljs-built_in">require</span>(<span class="hljs-string">&apos;anyproxy&apos;</span>);
<span class="hljs-keyword">const</span> exec = <span class="hljs-built_in">require</span>(<span class="hljs-string">&apos;child_process&apos;</span>).exec; <span class="hljs-keyword">const</span> exec = <span class="hljs-built_in">require</span>(<span class="hljs-string">&apos;child_process&apos;</span>).exec;
<span class="hljs-keyword">if</span> (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { <span class="hljs-keyword">if</span> (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
@ -946,6 +962,12 @@ anyproxy --intercept <span class="hljs-comment">#&#x542F;&#x52A8;AnyProxy&#xFF0C
<ul> <ul>
<li><a href="#&#x8BC1;&#x4E66;&#x914D;&#x7F6E;">&#x9644;&#x5F55;&#xFF1A;&#x5982;&#x4F55;&#x4FE1;&#x4EFB;CA&#x8BC1;&#x4E66;</a></li> <li><a href="#&#x8BC1;&#x4E66;&#x914D;&#x7F6E;">&#x9644;&#x5F55;&#xFF1A;&#x5982;&#x4F55;&#x4FE1;&#x4EFB;CA&#x8BC1;&#x4E66;</a></li>
</ul> </ul>
<h1 id="&#x4EE3;&#x7406;websocket">&#x4EE3;&#x7406;WebSocket</h1>
<pre><code class="lang-bash">anyproxy --ws-intercept
</code></pre>
<blockquote>
<p>&#x5F53;&#x542F;&#x7528;<code>HTTPS</code>&#x4EE3;&#x7406;&#x65F6;&#xFF0C;<code>wss</code>&#x4E5F;&#x4F1A;&#x88AB;&#x4EE3;&#x7406;&#xFF0C;&#x4F46;&#x662F;&#x4E0D;&#x4F1A;&#x88AB;AnyProxy&#x8BB0;&#x5F55;&#x3002;&#x9700;&#x8981;&#x5F00;&#x542F;<code>--ws-intercept</code>&#x540E;&#x624D;&#x4F1A;&#x4ECE;&#x754C;&#x9762;&#x4E0A;&#x770B;&#x5230;&#x76F8;&#x5E94;&#x5185;&#x5BB9;&#x3002;</p>
</blockquote>
<h1 id="rule&#x6A21;&#x5757;">rule&#x6A21;&#x5757;</h1> <h1 id="rule&#x6A21;&#x5757;">rule&#x6A21;&#x5757;</h1>
<p>AnyProxy&#x63D0;&#x4F9B;&#x4E86;&#x4E8C;&#x6B21;&#x5F00;&#x53D1;&#x7684;&#x80FD;&#x529B;&#xFF0C;&#x4F60;&#x53EF;&#x4EE5;&#x7528;js&#x7F16;&#x5199;&#x81EA;&#x5DF1;&#x7684;&#x89C4;&#x5219;&#x6A21;&#x5757;&#xFF08;rule&#xFF09;&#xFF0C;&#x6765;&#x81EA;&#x5B9A;&#x4E49;&#x7F51;&#x7EDC;&#x8BF7;&#x6C42;&#x7684;&#x5904;&#x7406;&#x903B;&#x8F91;&#x3002;</p> <p>AnyProxy&#x63D0;&#x4F9B;&#x4E86;&#x4E8C;&#x6B21;&#x5F00;&#x53D1;&#x7684;&#x80FD;&#x529B;&#xFF0C;&#x4F60;&#x53EF;&#x4EE5;&#x7528;js&#x7F16;&#x5199;&#x81EA;&#x5DF1;&#x7684;&#x89C4;&#x5219;&#x6A21;&#x5757;&#xFF08;rule&#xFF09;&#xFF0C;&#x6765;&#x81EA;&#x5B9A;&#x4E49;&#x7F51;&#x7EDC;&#x8BF7;&#x6C42;&#x7684;&#x5904;&#x7406;&#x903B;&#x8F91;&#x3002;</p>
<blockquote> <blockquote>
@ -1539,6 +1561,7 @@ newResponse.body += <span class="hljs-string">&apos;--from anyproxy--&apos;</spa
</ul> </ul>
</li> </li>
</ul> </ul>
<p>&#x4E0D;&#x540C;&#x5B89;&#x5353;&#x7CFB;&#x7EDF;&#x652F;&#x6301;&#x5B89;&#x88C5;&#x7684;&#x8BC1;&#x4E66;&#x6587;&#x4EF6;&#x7C7B;&#x578B;&#x4E0D;&#x5C3D;&#x76F8;&#x540C;&#xFF0C;&#x5927;&#x591A;&#x652F;&#x6301;&#x5B89;&#x88C5;&#x62D3;&#x5C55;&#x540D;&#x4E3A; .crt &#x7684;&#x8BC1;&#x4E66;&#x6587;&#x4EF6;&#xFF0C;&#x5C11;&#x90E8;&#x5206;&#x4EC5;&#x652F;&#x6301; .cer &#x6587;&#x4EF6;&#xFF08;&#x5DF2;&#x77E5;&#x5982; OPPO R15&#xFF09;&#xFF0C;AnyProxy &#x63D0;&#x4F9B;&#x4E86;&#x591A;&#x79CD;&#x7C7B;&#x578B;&#x7684;&#x8BC1;&#x4E66;&#x6587;&#x4EF6;&#xFF0C;&#x53EF;&#x5728;&#x4E0B;&#x8F7D;&#x5B89;&#x88C5;&#x65F6;&#x9009;&#x62E9;&#x3002;</p>
<h3 id="&#x914D;&#x7F6E;iosandroid&#x7CFB;&#x7EDF;&#x4EE3;&#x7406;">&#x914D;&#x7F6E;iOS/Android&#x7CFB;&#x7EDF;&#x4EE3;&#x7406;</h3> <h3 id="&#x914D;&#x7F6E;iosandroid&#x7CFB;&#x7EDF;&#x4EE3;&#x7406;">&#x914D;&#x7F6E;iOS/Android&#x7CFB;&#x7EDF;&#x4EE3;&#x7406;</h3>
<ul> <ul>
<li><p>&#x4EE3;&#x7406;&#x670D;&#x52A1;&#x5668;&#x90FD;&#x5728;wifi&#x8BBE;&#x7F6E;&#x4E2D;&#x914D;&#x7F6E;</p> <li><p>&#x4EE3;&#x7406;&#x670D;&#x52A1;&#x5668;&#x90FD;&#x5728;wifi&#x8BBE;&#x7F6E;&#x4E2D;&#x914D;&#x7F6E;</p>
@ -1636,7 +1659,7 @@ newResponse.body += <span class="hljs-string">&apos;--from anyproxy--&apos;</spa
<script> <script>
var gitbook = gitbook || []; var gitbook = gitbook || [];
gitbook.push(function() { 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> </script>
</div> </div>
@ -1646,6 +1669,10 @@ newResponse.body += <span class="hljs-string">&apos;--from anyproxy--&apos;</spa
<script src="../gitbook/theme.js"></script> <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> <script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>

File diff suppressed because one or more lines are too long

View File

@ -164,7 +164,7 @@ proxyServer.close();
* 样例 * 样例
```js ```js
const AnyProxy = require('AnyProxy'); const AnyProxy = require('anyproxy');
const exec = require('child_process').exec; const exec = require('child_process').exec;
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
@ -200,6 +200,14 @@ anyproxy --intercept #启动AnyProxy并解析所有https请求
* [附录如何信任CA证书](#证书配置) * [附录如何信任CA证书](#证书配置)
# 代理WebSocket
```bash
anyproxy --ws-intercept
```
> 当启用`HTTPS`代理时,`wss`也会被代理但是不会被AnyProxy记录。需要开启`--ws-intercept`后才会从界面上看到相应内容。
# rule模块 # rule模块
AnyProxy提供了二次开发的能力你可以用js编写自己的规则模块rule来自定义网络请求的处理逻辑。 AnyProxy提供了二次开发的能力你可以用js编写自己的规则模块rule来自定义网络请求的处理逻辑。
@ -620,6 +628,8 @@ module.exports = {
* 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装 * 设置 -> 安全性与位置信息 -> 加密与凭据 -> 从存储设备安装。找到你下载的证书文件,进行安装
* 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件进行安装 * 设置 -> 安全 -> 从SD卡安装证书。找到你下载的证书文件进行安装
不同安卓系统支持安装的证书文件类型不尽相同,大多支持安装拓展名为 .crt 的证书文件,少部分仅支持 .cer 文件(已知如 OPPO R15AnyProxy 提供了多种类型的证书文件,可在下载安装时选择。
### 配置iOS/Android系统代理 ### 配置iOS/Android系统代理
* 代理服务器都在wifi设置中配置 * 代理服务器都在wifi设置中配置

View File

@ -185,6 +185,22 @@
<li class="chapter " data-level="1.4" data-path="./"> <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"> <a href="./#rule-introduction">
@ -200,7 +216,7 @@
<ul class="articles"> <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"> <a href="./#sample">
@ -216,7 +232,7 @@
</li> </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"> <a href="./#how-does-it-work">
@ -232,7 +248,7 @@
</li> </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"> <a href="./#how-to-load-rule-module">
@ -253,7 +269,7 @@
</li> </li>
<li class="chapter " data-level="1.5" data-path="./"> <li class="chapter " data-level="1.6" data-path="./">
<a href="./#rule-module-interface"> <a href="./#rule-module-interface">
@ -270,7 +286,7 @@
<ul class="articles"> <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"> <a href="./#summary">
@ -286,7 +302,7 @@
</li> </li>
<li class="chapter " data-level="1.5.2" data-path="./"> <li class="chapter " data-level="1.6.2" data-path="./">
<a href="./#beforesendrequest"> <a href="./#beforesendrequest">
@ -302,7 +318,7 @@
</li> </li>
<li class="chapter " data-level="1.5.3" data-path="./"> <li class="chapter " data-level="1.6.3" data-path="./">
<a href="./#beforesendresponse"> <a href="./#beforesendresponse">
@ -318,7 +334,7 @@
</li> </li>
<li class="chapter " data-level="1.5.4" data-path="./"> <li class="chapter " data-level="1.6.4" data-path="./">
<a href="./#beforedealhttpsrequest"> <a href="./#beforedealhttpsrequest">
@ -334,7 +350,7 @@
</li> </li>
<li class="chapter " data-level="1.5.5" data-path="./"> <li class="chapter " data-level="1.6.5" data-path="./">
<a href="./#onerror"> <a href="./#onerror">
@ -350,7 +366,7 @@
</li> </li>
<li class="chapter " data-level="1.5.6" data-path="./"> <li class="chapter " data-level="1.6.6" data-path="./">
<a href="./#onconnecterror"> <a href="./#onconnecterror">
@ -371,7 +387,7 @@
</li> </li>
<li class="chapter " data-level="1.6" data-path="./"> <li class="chapter " data-level="1.7" data-path="./">
<a href="./#rule-samples"> <a href="./#rule-samples">
@ -388,7 +404,7 @@
<ul class="articles"> <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"> <a href="./#use-local-response">
@ -404,7 +420,7 @@
</li> </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"> <a href="./#modify-request-header">
@ -420,7 +436,7 @@
</li> </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"> <a href="./#modify-request-body">
@ -436,7 +452,7 @@
</li> </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"> <a href="./#modify-the-request-target">
@ -452,7 +468,7 @@
</li> </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"> <a href="./#modify-request-protocol">
@ -468,7 +484,7 @@
</li> </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"> <a href="./#modify-response-status-code">
@ -484,7 +500,7 @@
</li> </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"> <a href="./#modify-the-response-header">
@ -500,7 +516,7 @@
</li> </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"> <a href="./#modify-response-data-and-delay">
@ -521,7 +537,7 @@
</li> </li>
<li class="chapter " data-level="1.7" data-path="./"> <li class="chapter " data-level="1.8" data-path="./">
<a href="./#config-certification"> <a href="./#config-certification">
@ -538,7 +554,7 @@
<ul class="articles"> <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"> <a href="./#config-root-ca-in-osx">
@ -554,7 +570,7 @@
</li> </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"> <a href="./#config-root-ca-in-windows">
@ -570,7 +586,7 @@
</li> </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"> <a href="./#config-osx-system-proxy">
@ -586,7 +602,7 @@
</li> </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"> <a href="./#config-http-proxy-server">
@ -602,7 +618,7 @@
</li> </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"> <a href="./#trust-root-ca-in-ios">
@ -618,7 +634,7 @@
</li> </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"> <a href="./#trust-root-ca-in-ios-after-103">
@ -634,7 +650,7 @@
</li> </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"> <a href="./#trust-root-ca-in-android">
@ -650,7 +666,7 @@
</li> </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"> <a href="./#config-iosandroid-proxy-server">
@ -671,7 +687,7 @@
</li> </li>
<li class="chapter active" data-level="1.8" data-path="./"> <li class="chapter active" data-level="1.9" data-path="./">
<a href="./"> <a href="./">
@ -893,7 +909,7 @@ AnyProxy.utils.systemProxyMgr.disableGlobalProxy();
</li> </li>
<li>Sample</li> <li>Sample</li>
</ul> </ul>
<pre><code class="lang-js"> <span class="hljs-keyword">const</span> AnyProxy = <span class="hljs-built_in">require</span>(<span class="hljs-string">&apos;AnyProxy&apos;</span>); <pre><code class="lang-js"> <span class="hljs-keyword">const</span> AnyProxy = <span class="hljs-built_in">require</span>(<span class="hljs-string">&apos;anyproxy&apos;</span>);
<span class="hljs-keyword">const</span> exec = <span class="hljs-built_in">require</span>(<span class="hljs-string">&apos;child_process&apos;</span>).exec; <span class="hljs-keyword">const</span> exec = <span class="hljs-built_in">require</span>(<span class="hljs-string">&apos;child_process&apos;</span>).exec;
<span class="hljs-keyword">if</span> (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { <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> <ul>
<li><a href="#config-certification">Appendix&#xFF1A;how to trust CA</a></li> <li><a href="#config-certification">Appendix&#xFF1A;how to trust CA</a></li>
</ul> </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> <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> <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> <blockquote>
@ -1510,6 +1532,7 @@ newResponse.body += <span class="hljs-string">&apos;--from anyproxy--&apos;</spa
<ul> <ul>
<li>Besides installing root CA, you have to &quot;turn on&quot; 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> <li>Besides installing root CA, you have to &quot;turn on&quot; 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> </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> <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. <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> 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> </ul>
</li> </li>
</ul> </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> <h3 id="config-iosandroid-proxy-server">config iOS/Android proxy server</h3>
<ul> <ul>
<li><p>proxy settings are placed in wifi setting</p> <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> <script>
var gitbook = gitbook || []; var gitbook = gitbook || [];
gitbook.push(function() { 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> </script>
</div> </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/theme.js"></script>
<script src="../gitbook/gitbook-plugin-livereload/plugin.js"></script>
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script> <script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>

File diff suppressed because one or more lines are too long

View File

@ -163,7 +163,7 @@ proxyServer.close();
* Sample * Sample
```js ```js
const AnyProxy = require('AnyProxy'); const AnyProxy = require('anyproxy');
const exec = require('child_process').exec; const exec = require('child_process').exec;
if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
@ -200,6 +200,13 @@ anyproxy --intercept #launch anyproxy and intercept all https traffic
* [Appendixhow to trust CA](#config-certification) * [Appendixhow 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 # 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. 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. * 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 ### 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. 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. 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 & 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 * 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 ### config iOS/Android proxy server

File diff suppressed because one or more lines are too long

189
jest.config.js Normal file
View 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,
};

View File

@ -1,11 +1,16 @@
'use strict' 'use strict'
const util = require('./util');
const EasyCert = require('node-easy-cert'); const EasyCert = require('node-easy-cert');
const co = require('co'); const co = require('co');
const os = require('os');
const inquirer = require('inquirer');
const util = require('./util');
const logUtil = require('./log');
const options = { const options = {
rootDirPath: util.getAnyProxyPath('certificates'), rootDirPath: util.getAnyProxyPath('certificates'),
inMemory: false,
defaultCertAttrs: [ defaultCertAttrs: [
{ name: 'countryName', value: 'CN' }, { name: 'countryName', value: 'CN' },
{ name: 'organizationName', value: 'AnyProxy' }, { 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; module.exports = crtMgr;

View File

@ -4,19 +4,28 @@
const async = require('async'), const async = require('async'),
https = require('https'), https = require('https'),
tls = require('tls'), tls = require('tls'),
assert = require('assert'),
crypto = require('crypto'), crypto = require('crypto'),
color = require('colorful'), color = require('colorful'),
certMgr = require('./certMgr'), certMgr = require('./certMgr'),
logUtil = require('./log'), logUtil = require('./log'),
util = require('./util'), util = require('./util'),
wsServerMgr = require('./wsServerMgr'), wsServerMgr = require('./wsServerMgr'),
co = require('co'),
constants = require('constants'), constants = require('constants'),
asyncTask = require('async-task-mgr'); asyncTask = require('async-task-mgr');
const createSecureContext = tls.createSecureContext || crypto.createSecureContext; /**
//using sni to avoid multiple ports * Create an https server
function SNIPrepareCert(serverName, SNICallback) { *
* @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;
function SNIPrepareCert(serverName, SNICallback) {
let keyContent, let keyContent,
crtContent, crtContent,
ctx; ctx;
@ -52,112 +61,67 @@ function SNIPrepareCert(serverName, SNICallback) {
} else { } else {
logUtil.printLog('err occurred when prepare certs for SNI - ' + err, logUtil.T_ERR); 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); 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) => { return new Promise((resolve) => {
certMgr.getCertificate('anyproxy_internal_https_server', (err, keyContent, crtContent) => {
const server = https.createServer({ const server = https.createServer({
secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1, secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
SNICallback: SNIPrepareCert, SNICallback: SNIPrepareCert,
key: keyContent, }, handler).listen(port);
cert: crtContent
}, config.handler).listen(config.port);
resolve(server); resolve(server);
}); });
});
} }
/** function createHttpsIPServer(ip, port, handler) {
* create an https server that serving on IP address assert(ip && port && handler, 'invalid param for https IP server');
* @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'));
}
if (!config.ip) { return new Promise((resolve, reject) => {
throw (new Error('please assign an IP to create the https server')); certMgr.getCertificate(ip, (err, keyContent, crtContent) => {
} if (err) return reject(err);
return new Promise((resolve) => {
certMgr.getCertificate(config.ip, (err, keyContent, crtContent) => {
const server = https.createServer({ const server = https.createServer({
secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1, secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
key: keyContent, key: keyContent,
cert: crtContent cert: crtContent,
}, config.handler).listen(config.port); }, handler).listen(port);
resolve(server); resolve(server);
}); });
}); });
} }
/**
*
*
* @class httpsServerMgr
* @param {object} config
* @param {function} config.handler handler to deal https request
*
*/
class httpsServerMgr { class httpsServerMgr {
constructor(config) { constructor(config) {
if (!config || !config.handler) { if (!config || !config.handler) {
throw new Error('handler is required'); throw new Error('handler is required');
} }
this.instanceDefaultHost = '127.0.0.1';
this.httpsAsyncTask = new asyncTask(); this.httpsAsyncTask = new asyncTask();
this.handler = config.handler; this.handler = config.handler;
this.wsHandler = config.wsHandler this.wsHandler = config.wsHandler
this.asyncSNITaskName = `https_SNI_${Math.random()}`;
this.activeServers = [];
} }
getSharedHttpsServer(hostname) { getSharedHttpsServer(hostname) {
// ip address will have a unique name
const finalHost = util.isIpDomain(hostname) ? hostname : this.instanceDefaultHost;
const self = this; const self = this;
function prepareServer(callback) { const ifIPHost = hostname && util.isIp(hostname);
let instancePort; const serverHost = '127.0.0.1';
co(util.getFreePort)
.then(co.wrap(function *(port) {
instancePort = port;
let httpsServer = null;
// if ip address passed in, will create an IP http server function prepareServer(callback) {
if (util.isIpDomain(hostname)) { let port;
httpsServer = yield createIPHttpsServer({ Promise.resolve(util.getFreePort())
ip: hostname, .then(freePort => {
port, port = freePort;
handler: self.handler if (ifIPHost) {
}); return createHttpsIPServer(hostname, port, self.handler);
} else { } else {
httpsServer = yield createHttpsServer({ return createHttpsSNIServer(port, self.handler);
port,
handler: self.handler
});
} }
})
.then(httpsServer => {
self.activeServers.push(httpsServer);
wsServerMgr.getWsServer({ wsServerMgr.getWsServer({
server: httpsServer, server: httpsServer,
@ -169,21 +133,19 @@ class httpsServerMgr {
}); });
const result = { const result = {
host: finalHost, host: serverHost,
port: instancePort, port,
}; };
callback(null, result); callback(null, result);
return result; })
}))
.catch(e => { .catch(e => {
callback(e); callback(e);
}); });
} }
// same server for same host
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// each ip address will gain a unit task name, self.httpsAsyncTask.addTask(ifIPHost ? hostname : serverHost, prepareServer, (error, serverInfo) => {
// while the domain address will share a common task name
self.httpsAsyncTask.addTask(`createHttpsServer-${finalHost}`, prepareServer, (error, serverInfo) => {
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
@ -192,6 +154,12 @@ class httpsServerMgr {
}); });
}); });
} }
close() {
this.activeServers.forEach(server => {
server.close();
});
}
} }
module.exports = httpsServerMgr; module.exports = httpsServerMgr;

View File

@ -58,7 +58,7 @@ function printLog(content, type) {
return; return;
} }
console.error(color.magenta(`[AnyProxy WARN][${timeString}]: ` + content)); console.error(color.yellow(`[AnyProxy WARN][${timeString}]: ` + content));
break; break;
} }
@ -89,7 +89,7 @@ module.exports.warn = (content) => {
}; };
module.exports.error = (content) => { module.exports.error = (content) => {
printLog(content, LogLevelMap.error); printLog(content, LogLevelMap.system_error);
}; };
module.exports.ruleError = (content) => { module.exports.ruleError = (content) => {

View File

@ -31,7 +31,7 @@ const WS_MESSAGE_FILE_PRFIX = 'ws_message_';
const CACHE_DIR_PREFIX = 'cache_r'; const CACHE_DIR_PREFIX = 'cache_r';
function getCacheDir() { function getCacheDir() {
const rand = Math.floor(Math.random() * 1000000), 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); fs.mkdirSync(cachePath);
return cachePath; return cachePath;
@ -86,11 +86,22 @@ class Recorder extends events.EventEmitter {
this.globalId = 1; this.globalId = 1;
this.cachePath = getCacheDir(); this.cachePath = getCacheDir();
this.db = new Datastore(); this.db = new Datastore();
this.db.persistence.setAutocompactionInterval(5001);
this.recordBodyMap = []; // id - body 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) { emitUpdate(id, info) {
const self = this; const self = this;
if (info) { if (info) {
@ -126,12 +137,12 @@ class Recorder extends events.EventEmitter {
* *
*/ */
updateRecordWsMessage(id, message) { updateRecordWsMessage(id, message) {
const cachePath = this.cachePath;
if (id < 0) return; if (id < 0) return;
try { 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) + ',', () => {}); fs.appendFile(recordWsMessageFile, wsMessageStingify(message) + ',', () => {});
});
} catch (e) { } catch (e) {
console.error(e); console.error(e);
logUtil.error(e.message + e.stack); logUtil.error(e.message + e.stack);
@ -172,15 +183,16 @@ class Recorder extends events.EventEmitter {
updateRecordBody(id, info) { updateRecordBody(id, info) {
const self = this; const self = this;
const cachePath = self.cachePath;
if (id === -1) return; if (id === -1) return;
if (!id || typeof info.resBody === 'undefined') return; if (!id || typeof info.resBody === 'undefined') return;
//add to body map //add to body map
//ignore image data //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, () => {}); fs.writeFile(bodyFile, info.resBody, () => {});
});
} }
/** /**
@ -189,13 +201,16 @@ class Recorder extends events.EventEmitter {
*/ */
getBody(id, cb) { getBody(id, cb) {
const self = this; const self = this;
const cachePath = self.cachePath;
if (id < 0) { if (id < 0) {
cb && cb(''); 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) => { fs.access(bodyFile, fs.F_OK || fs.R_OK, (err) => {
if (err) { if (err) {
cb && cb(err); cb && cb(err);
@ -203,6 +218,7 @@ class Recorder extends events.EventEmitter {
fs.readFile(bodyFile, cb); fs.readFile(bodyFile, cb);
} }
}); });
});
} }
getDecodedBody(id, cb) { getDecodedBody(id, cb) {
@ -242,18 +258,16 @@ class Recorder extends events.EventEmitter {
bodyContent = iconv.decode(bodyContent, currentCharset); bodyContent = iconv.decode(bodyContent, currentCharset);
} }
result.mime = contentType;
result.content = bodyContent.toString(); result.content = bodyContent.toString();
result.type = contentType && /application\/json/i.test(contentType) ? 'json' : 'text'; result.type = contentType && /application\/json/i.test(contentType) ? 'json' : 'text';
} else if (contentType && /image/i.test(contentType)) { } else if (contentType && /image/i.test(contentType)) {
result.type = 'image'; result.type = 'image';
result.mime = contentType;
result.content = bodyContent; result.content = bodyContent;
} else { } else {
result.type = contentType; result.type = contentType;
result.mime = contentType;
result.content = bodyContent.toString(); result.content = bodyContent.toString();
} }
result.mime = contentType;
result.fileName = path.basename(record.path); result.fileName = path.basename(record.path);
result.statusCode = record.statusCode; result.statusCode = record.statusCode;
} catch (e) { } catch (e) {
@ -270,14 +284,16 @@ class Recorder extends events.EventEmitter {
* *
*/ */
getDecodedWsMessage(id, cb) { getDecodedWsMessage(id, cb) {
const self = this;
const cachePath = self.cachePath;
if (id < 0) { if (id < 0) {
cb && cb([]); 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) => { fs.access(wsMessageFile, fs.F_OK || fs.R_OK, (err) => {
if (err) { if (err) {
cb && cb(err); cb && cb(err);
@ -301,6 +317,7 @@ class Recorder extends events.EventEmitter {
}); });
} }
}); });
});
} }
getSingleRecord(id, cb) { getSingleRecord(id, cb) {
@ -327,9 +344,23 @@ class Recorder extends events.EventEmitter {
} }
clear() { clear() {
logUtil.printLog('clearing cache file...');
const self = this; const self = this;
proxyUtil.deleteFolderContentsRecursive(self.cachePath, true); 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; module.exports = Recorder;

View File

@ -44,7 +44,7 @@ const getErrorResponse = (error, fullUrl) => {
header: { header: {
'Content-Type': 'text/html; charset=utf-8', 'Content-Type': 'text/html; charset=utf-8',
'Proxy-Error': true, 'Proxy-Error': true,
'Proxy-Error-Message': error || 'null' 'Proxy-Error-Message': error ? JSON.stringify(error) : 'null'
}, },
body: requestErrorHandler.getErrorContent(error, fullUrl) body: requestErrorHandler.getErrorContent(error, fullUrl)
}; };
@ -120,7 +120,7 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
// only do unzip when there is res data // only do unzip when there is res data
if (ifServerGzipped && originContentLen) { if (ifServerGzipped && originContentLen) {
refactContentEncoding(); refactContentEncoding();
zlib.gunzip(serverResData, (err, buff) => { // TODO test case to cover zlib.gunzip(serverResData, (err, buff) => {
if (err) { if (err) {
rejectParsing(err); rejectParsing(err);
} else { } else {
@ -129,7 +129,7 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
}); });
} else if (isServerDeflated && originContentLen) { } else if (isServerDeflated && originContentLen) {
refactContentEncoding(); refactContentEncoding();
zlib.inflateRaw(serverResData, (err, buff) => { // TODO test case to cover zlib.inflate(serverResData, (err, buff) => {
if (err) { if (err) {
rejectParsing(err); rejectParsing(err);
} else { } else {
@ -212,18 +212,39 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
@param @required wsClient the ws client of WebSocket @param @required wsClient the ws client of WebSocket
* *
*/ */
function getWsReqInfo(wsClient) { function getWsReqInfo(wsReq) {
const upgradeReq = wsClient.upgradeReq || {}; const headers = wsReq.headers || {};
const header = upgradeReq.headers || {}; const host = headers.host;
const host = header.host;
const hostName = host.split(':')[0]; const hostName = host.split(':')[0];
const port = host.split(':')[1]; const port = host.split(':')[1];
// TODO 如果是windows机器url是不是全路径需要对其过滤取出 // 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 { return {
headers: headers, // the full headers of origin ws connection
noWsHeaders: getNoWsHeaders(),
hostName: hostName, hostName: hostName,
port: port, port: port,
path: path, path: path,
@ -251,7 +272,15 @@ function getUserReqHandler(userRule, recorder) {
const host = req.headers.host; const host = req.headers.host;
const protocol = (!!req.connection.encrypted && !(/^http:/).test(req.url)) ? 'https' : 'http'; const protocol = (!!req.connection.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 urlPattern = url.parse(fullUrl);
const path = urlPattern.path; const path = urlPattern.path;
@ -529,14 +558,14 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
shouldIntercept = reqHandlerCtx.forceProxyHttps; shouldIntercept = reqHandlerCtx.forceProxyHttps;
} }
}) })
.then(() => .then(() => {
new Promise((resolve) => { return new Promise((resolve) => {
// mark socket connection as established, to detect the request protocol // mark socket connection as established, to detect the request protocol
cltSocket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', resolve); cltSocket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', resolve);
});
}) })
) .then(() => {
.then(() => return new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
let resolved = false; let resolved = false;
cltSocket.on('data', (chunk) => { cltSocket.on('data', (chunk) => {
requestStream.push(chunk); requestStream.push(chunk);
@ -559,11 +588,19 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
resolve(); 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', () => { cltSocket.on('end', () => {
requestStream.push(null); requestStream.push(null);
}); });
});
}) })
)
.then((result) => { .then((result) => {
// log and recorder // log and recorder
if (shouldIntercept) { if (shouldIntercept) {
@ -664,7 +701,7 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
* get a websocket event handler * get a websocket event handler
@param @required {object} wsClient @param @required {object} wsClient
*/ */
function getWsHandler(userRule, recorder, wsClient) { function getWsHandler(userRule, recorder, wsClient, wsReq) {
const self = this; const self = this;
try { try {
let resourceInfoId = -1; let resourceInfoId = -1;
@ -672,10 +709,12 @@ function getWsHandler(userRule, recorder, wsClient) {
wsMessages: [] // all ws messages go through AnyProxy wsMessages: [] // all ws messages go through AnyProxy
}; };
const clientMsgQueue = []; const clientMsgQueue = [];
const serverInfo = getWsReqInfo(wsClient); const serverInfo = getWsReqInfo(wsReq);
const wsUrl = `${serverInfo.protocol}://${serverInfo.hostName}:${serverInfo.port}${serverInfo.path}`; const serverInfoPort = serverInfo.port ? `:${serverInfo.port}` : '';
const wsUrl = `${serverInfo.protocol}://${serverInfo.hostName}${serverInfoPort}${serverInfo.path}`;
const proxyWs = new WebSocket(wsUrl, '', { const proxyWs = new WebSocket(wsUrl, '', {
rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized,
headers: serverInfo.noWsHeaders
}); });
if (recorder) { if (recorder) {
@ -684,7 +723,7 @@ function getWsHandler(userRule, recorder, wsClient) {
method: 'WebSocket', method: 'WebSocket',
path: serverInfo.path, path: serverInfo.path,
url: wsUrl, url: wsUrl,
req: wsClient.upgradeReq || {}, req: wsReq,
startTime: new Date().getTime() startTime: new Date().getTime()
}); });
resourceInfoId = recorder.appendRecord(resourceInfo); 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 // 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(); resourceInfo.endTime = new Date().getTime();
const headers = response.headers;
resourceInfo.res = { //construct a self-defined res object resourceInfo.res = { //construct a self-defined res object
statusCode: response.statusCode, statusCode: response.statusCode,
headers: headers, headers: headers,
@ -813,7 +853,6 @@ function getWsHandler(userRule, recorder, wsClient) {
} }
class RequestHandler { class RequestHandler {
/** /**
* Creates an instance of RequestHandler. * Creates an instance of RequestHandler.
* *
@ -854,7 +893,8 @@ class RequestHandler {
reqHandlerCtx.httpsServerMgr = new HttpsServerMgr({ reqHandlerCtx.httpsServerMgr = new HttpsServerMgr({
handler: reqHandlerCtx.userRequestHandler, 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]); this.connectReqHandler = getConnectReqHandler.apply(reqHandlerCtx, [userRule, recorder, reqHandlerCtx.httpsServerMgr]);

View File

@ -5,7 +5,7 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const request = require('request'); const request = require('request');
const cachePath = proxyUtil.getAnyProxyPath('cache'); const cachePath = proxyUtil.getAnyProxyTmpPath();
/** /**
* download a file and cache * download a file and cache

View File

@ -66,4 +66,16 @@ module.exports = {
*onConnectError(requestDetail, error) { *onConnectError(requestDetail, error) {
return null; return null;
}, },
/**
*
*
* @param {any} requestDetail
* @param {any} error
* @returns
*/
*onClientSocketError(requestDetail, error) {
return null;
},
}; };

View File

@ -4,10 +4,12 @@ const fs = require('fs'),
path = require('path'), path = require('path'),
mime = require('mime-types'), mime = require('mime-types'),
color = require('colorful'), color = require('colorful'),
crypto = require('crypto'), child_process = require('child_process'),
os = require('os'),
Buffer = require('buffer').Buffer, Buffer = require('buffer').Buffer,
logUtil = require('./log'); logUtil = require('./log');
const networkInterfaces = require('os').networkInterfaces();
const networkInterfaces = os.networkInterfaces();
// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"} // {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
module.exports.lower_keys = (obj) => { module.exports.lower_keys = (obj) => {
@ -52,6 +54,14 @@ module.exports.getAnyProxyPath = function (pathName) {
return targetPath; 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) { module.exports.simpleRender = function (str, object, regexp) {
return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => { return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
if (match.charAt(0) === '\\') { if (match.charAt(0) === '\\') {
@ -298,7 +308,7 @@ module.exports.getByteSize = function (content) {
/* /*
* identify whether the * identify whether the
*/ */
module.exports.isIpDomain = function (domain) { module.exports.isIp = function (domain) {
if (!domain) { if (!domain) {
return false; return false;
} }
@ -307,17 +317,22 @@ module.exports.isIpDomain = function (domain) {
return ipReg.test(domain); return ipReg.test(domain);
}; };
/** module.exports.execScriptSync = function (cmd) {
* To generic a Sec-WebSocket-Accept value let stdout,
* 1. append the `Sec-WebSocket-Key` request header with `matic string` status = 0;
* 2. get sha1 hash of the string try {
* 3. get base64 of the sha1 hash stdout = child_process.execSync(cmd);
*/ } catch (err) {
module.exports.genericWsSecAccept = function (wsSecKey) { stdout = err.stdout;
// the string to generate the Sec-WebSocket-Accept status = err.status;
const magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; }
const targetString = `${wsSecKey}${magicString}`;
const shasum = crypto.createHash('sha1'); return {
shasum.update(targetString); stdout: stdout.toString(),
return shasum.digest('base64'); status
} };
};
module.exports.guideToHomePage = function () {
logUtil.info('Refer to http://anyproxy.io for more detail');
};

View File

@ -1,7 +1,5 @@
'use strict'; 'use strict';
const DEFAULT_WEB_PORT = 8002; // port for web interface
const express = require('express'), const express = require('express'),
url = require('url'), url = require('url'),
bodyParser = require('body-parser'), bodyParser = require('body-parser'),
@ -14,11 +12,16 @@ const express = require('express'),
wsServer = require('./wsServer'), wsServer = require('./wsServer'),
juicer = require('juicer'), juicer = require('juicer'),
ip = require('ip'), 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 packageJson = require('../package.json');
const MAX_CONTENT_SIZE = 1024 * 2000; // 2000kb 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} * @extends {events.EventEmitter}
*/ */
class webInterface extends events.EventEmitter { class webInterface extends events.EventEmitter {
/** /**
* Creates an instance of webInterface. * Creates an instance of webInterface.
* *
@ -68,10 +70,9 @@ class webInterface extends events.EventEmitter {
customMenu = ''; // userRule._getCustomMenu(); customMenu = ''; // userRule._getCustomMenu();
} catch (e) { } } catch (e) { }
const myAbsAddress = 'http://' + ipAddress + ':' + self.webPort + '/', const staticDir = path.join(__dirname, '../', webBasePath);
staticDir = path.join(__dirname, '../', webBasePath);
const app = express(); const app = express();
app.use(compress()); //invoke gzip app.use(compress()); //invoke gzip
app.use((req, res, next) => { app.use((req, res, next) => {
res.setHeader('note', 'THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE'); res.setHeader('note', 'THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE');
@ -147,10 +148,8 @@ class webInterface extends events.EventEmitter {
if (err || !result) { if (err || !result) {
res.json({}); res.json({});
} else if (result.statusCode === 200 && result.mime) { } else if (result.statusCode === 200 && result.mime) {
if (result.type === 'json' ||
result.mime.indexOf('text') === 0 ||
// deal with 'application/x-javascript' and 'application/javascript' // deal with 'application/x-javascript' and 'application/javascript'
result.mime.indexOf('javascript') > -1) { if (/json|text|javascript/.test(result.mime)) {
_resContent(); _resContent();
} else if (result.type === 'image') { } else if (result.type === 'image') {
_resDownload(false); _resDownload(false);
@ -162,7 +161,7 @@ class webInterface extends events.EventEmitter {
} }
}); });
} else { } 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) => { app.get('/fetchCrtFile', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Origin', '*');
const _crtFilePath = certMgr.getRootCAFilePath(); const _crtFilePath = certMgr.getRootCAFilePath();
if (_crtFilePath) { 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-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 })); res.end(fs.readFileSync(_crtFilePath, { encoding: null }));
} else { } else {
res.setHeader('Content-Type', 'text/html'); 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) => { app.get('/api/getQrCode', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*'); 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.addData(targetUrl);
qr.make(); 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({ res.json({
status: 'success', status: 'success',
url: targetUrl, url: targetUrl,
isRootCAFileExists, isRootCAFileExists,
qrImgDom: qrImageTag qrImgDom: qr.createImgTag(4)
}); });
}); });
@ -319,11 +307,15 @@ class webInterface extends events.EventEmitter {
} }
close() { close() {
this.server && this.server.close(); const self = this;
this.wsServer && this.wsServer.closeAll(); return new Promise((resolve, reject) => {
this.server = null; self.server && self.server.close();
this.wsServer = null; self.wsServer && self.wsServer.closeAll();
this.proxyInstance = null; self.server = null;
self.wsServer = null;
self.proxyInstance = null;
resolve();
});
} }
} }

View File

@ -59,6 +59,7 @@ class wsServer {
const self = this; const self = this;
self.config = config; self.config = config;
self.recorder = recorder; self.recorder = recorder;
self.checkBroadcastFlagTimer = null;
} }
start() { start() {
@ -78,7 +79,7 @@ class wsServer {
// the flat to indicate wheter to broadcast the record // the flat to indicate wheter to broadcast the record
let broadcastFlag = true; let broadcastFlag = true;
setInterval(() => { self.checkBroadcastFlagTimer = setInterval(() => {
broadcastFlag = true; broadcastFlag = true;
sendMultipleMessage(); sendMultipleMessage();
}, 50); }, 50);
@ -102,7 +103,7 @@ class wsServer {
try { try {
data = JSON.stringify(data); data = JSON.stringify(data);
} catch (e) { } catch (e) {
console.error('==> errorr when do broadcast ', e, data); console.error('==> error when do broadcast ', e, data);
} }
} }
for (const client of wss.clients) { for (const client of wss.clients) {
@ -161,6 +162,9 @@ class wsServer {
closeAll() { closeAll() {
const self = this; const self = this;
if (self.checkBroadcastFlagTimer) {
clearInterval(self.checkBroadcastFlagTimer);
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
self.wss.close((e) => { self.wss.close((e) => {
if (e) { if (e) {

View File

@ -1,6 +1,6 @@
{ {
"name": "anyproxy", "name": "anyproxy",
"version": "4.0.2", "version": "4.1.3",
"description": "A fully configurable HTTP/HTTPS proxy in Node.js", "description": "A fully configurable HTTP/HTTPS proxy in Node.js",
"main": "proxy.js", "main": "proxy.js",
"bin": { "bin": {
@ -23,28 +23,30 @@
"express": "^4.8.5", "express": "^4.8.5",
"fast-json-stringify": "^0.17.0", "fast-json-stringify": "^0.17.0",
"iconv-lite": "^0.4.6", "iconv-lite": "^0.4.6",
"inquirer": "^3.0.1", "inquirer": "^5.2.0",
"ip": "^0.3.2", "ip": "^0.3.2",
"juicer": "^0.6.6-stable", "juicer": "^0.6.6-stable",
"mime-types": "2.1.11", "mime-types": "2.1.11",
"moment": "^2.15.1", "moment": "^2.15.1",
"nedb": "^1.8.0", "nedb": "^1.8.0",
"node-easy-cert": "^1.0.0", "node-easy-cert": "^1.0.0",
"node-forge": "^0.6.39",
"npm": "^2.7.0",
"pug": "^2.0.0-beta6", "pug": "^2.0.0-beta6",
"q": "^1.4.1",
"qrcode-npm": "0.0.3", "qrcode-npm": "0.0.3",
"request": "^2.74.0", "request": "^2.74.0",
"stream-throttle": "^0.1.3", "stream-throttle": "^0.1.3",
"svg-inline-react": "^1.0.2",
"thunkify": "^2.1.2",
"whatwg-fetch": "^1.0.0", "whatwg-fetch": "^1.0.0",
"ws": "^2.2.0" "ws": "^5.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.3",
"@babel/preset-env": "^7.8.3",
"antd": "^2.5.0", "antd": "^2.5.0",
"autoprefixer": "^6.4.1", "autoprefixer": "^6.4.1",
"babel-core": "^6.14.0", "babel-core": "^6.14.0",
"babel-eslint": "^7.0.0", "babel-eslint": "^7.0.0",
"babel-jest": "^24.9.0",
"babel-loader": "^6.2.5", "babel-loader": "^6.2.5",
"babel-plugin-import": "^1.0.0", "babel-plugin-import": "^1.0.0",
"babel-plugin-transform-runtime": "^6.15.0", "babel-plugin-transform-runtime": "^6.15.0",
@ -55,23 +57,16 @@
"babel-register": "^6.11.6", "babel-register": "^6.11.6",
"babel-runtime": "^6.11.6", "babel-runtime": "^6.11.6",
"css-loader": "^0.23.1", "css-loader": "^0.23.1",
"eslint": "^3.5.0", "eslint": ">=4.18.2",
"eslint-config-airbnb": "^15.1.0", "eslint-config-airbnb": "^15.1.0",
"eslint-plugin-import": "^2.7.0", "eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^5.1.1", "eslint-plugin-jsx-a11y": "^5.1.1",
"eslint-plugin-react": "^7.4.0", "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", "file-loader": "^0.9.0",
"https-proxy-agent": "^1.0.0", "jest": "^24.9.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",
"less": "^2.7.1", "less": "^2.7.1",
"less-loader": "^2.2.3", "less-loader": "^2.2.3",
"memwatch-next": "^0.3.0",
"node-simhash": "^0.1.0", "node-simhash": "^0.1.0",
"nodeunit": "^0.9.1", "nodeunit": "^0.9.1",
"phantom": "^4.0.0", "phantom": "^4.0.0",
@ -88,19 +83,19 @@
"stream-equal": "0.1.8", "stream-equal": "0.1.8",
"style-loader": "^0.13.1", "style-loader": "^0.13.1",
"svg-inline-loader": "^0.7.1", "svg-inline-loader": "^0.7.1",
"svg-inline-react": "^1.0.2", "tunnel": "^0.0.6",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"webpack": "^1.12.9", "urllib": "^2.34.2",
"webpack": "^3.10.0",
"worker-loader": "^0.7.1" "worker-loader": "^0.7.1"
}, },
"scripts": { "scripts": {
"prepublish": "npm run buildweb", "prepublish": "npm run buildweb",
"test": "node test.js", "test": "npx jest",
"lint": "eslint .", "lint": "eslint .",
"testserver": "node test/server/startServer.js", "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 --colors",
"buildweb": "NODE_ENV=production webpack --config web/webpack.config.js --process --colors", "webserver": "NODE_ENV=test webpack --config web/webpack.config.js --colors --watch",
"webserver": "NODE_ENV=test webpack --config web/webpack.config.js --process --colors --watch",
"doc:serve": "node build_scripts/prebuild-doc.js && gitbook serve ./docs-src ./docs --log debug", "doc:serve": "node build_scripts/prebuild-doc.js && gitbook serve ./docs-src ./docs --log debug",
"doc:build": "./build_scripts/build-doc-site.sh" "doc:build": "./build_scripts/build-doc-site.sh"
}, },
@ -112,5 +107,8 @@
"url": "https://github.com/alibaba/anyproxy" "url": "https://github.com/alibaba/anyproxy"
}, },
"author": "ottomao@gmail.com", "author": "ottomao@gmail.com",
"license": "Apache-2.0" "license": "Apache-2.0",
"engines": {
"node": ">=6.0.0"
}
} }

View File

@ -14,25 +14,6 @@ const http = require('http'),
wsServerMgr = require('./lib/wsServerMgr'), wsServerMgr = require('./lib/wsServerMgr'),
ThrottleGroup = require('stream-throttle').ThrottleGroup; 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', const T_TYPE_HTTP = 'http',
T_TYPE_HTTPS = 'https', T_TYPE_HTTPS = 'https',
DEFAULT_TYPE = T_TYPE_HTTP; DEFAULT_TYPE = T_TYPE_HTTP;
@ -47,7 +28,6 @@ const PROXY_STATUS_CLOSED = 'CLOSED';
* @extends {events.EventEmitter} * @extends {events.EventEmitter}
*/ */
class ProxyCore extends events.EventEmitter { class ProxyCore extends events.EventEmitter {
/** /**
* Creates an instance of ProxyCore. * Creates an instance of ProxyCore.
* *
@ -135,7 +115,7 @@ class ProxyCore extends events.EventEmitter {
*/ */
handleExistConnections(socket) { handleExistConnections(socket) {
const self = this; const self = this;
self.socketIndex ++; self.socketIndex++;
const key = `socketIndex_${self.socketIndex}`; const key = `socketIndex_${self.socketIndex}`;
self.socketPool[key] = socket; self.socketPool[key] = socket;
@ -248,7 +228,6 @@ class ProxyCore extends events.EventEmitter {
return self; return self;
} }
/** /**
* close the proxy server * close the proxy server
* *
@ -271,10 +250,14 @@ class ProxyCore extends events.EventEmitter {
for (const cltSocketItem of this.requestHandler.cltSockets) { for (const cltSocketItem of this.requestHandler.cltSockets) {
const key = cltSocketItem[0]; const key = cltSocketItem[0];
const cltSocket = cltSocketItem[1]; const cltSocket = cltSocketItem[1];
logUtil.printLog(`endding https cltSocket : ${key}`); logUtil.printLog(`closing https cltSocket : ${key}`);
cltSocket.end(); cltSocket.end();
} }
if (this.requestHandler.httpsServerMgr) {
this.requestHandler.httpsServerMgr.close();
}
if (this.socketPool) { if (this.socketPool) {
for (const key in this.socketPool) { for (const key in this.socketPool) {
this.socketPool[key].destroy(); this.socketPool[key].destroy();
@ -326,51 +309,45 @@ class ProxyServer extends ProxyCore {
} }
start() { start() {
if (this.recorder) {
this.recorder.setDbAutoCompact();
}
// start web interface if neeeded // start web interface if neeeded
if (this.proxyWebinterfaceConfig && this.proxyWebinterfaceConfig.enable) { if (this.proxyWebinterfaceConfig && this.proxyWebinterfaceConfig.enable) {
this.webServerInstance = new WebInterface(this.proxyWebinterfaceConfig, this.recorder); this.webServerInstance = new WebInterface(this.proxyWebinterfaceConfig, this.recorder);
}
// start web server // start web server
this.webServerInstance.start().then(() => { this.webServerInstance.start()
// start proxy core // start proxy core
.then(() => {
super.start(); super.start();
}) })
.catch((e) => { .catch((e) => {
this.emit('error', e); this.emit('error', e);
}); });
} else {
super.start();
}
} }
close() { close() {
return new Promise((resolve, reject) => { const self = this;
super.close() // release recorder
.then((error) => { if (self.recorder) {
if (error) { self.recorder.stopDbAutoCompact();
resolve(error); self.recorder.clear();
} }
}); self.recorder = null;
if (this.recorder) { // close ProxyCore
logUtil.printLog('clearing cache file...'); return super.close()
this.recorder.clear(); // release webInterface
} .then(() => {
const tmpWebServer = this.webServerInstance; if (self.webServerInstance) {
this.recorder = null; const tmpWebServer = self.webServerInstance;
this.webServerInstance = null; self.webServerInstance = null;
if (tmpWebServer) { logUtil.printLog('closing webInterface...');
logUtil.printLog('closing webserver...'); return tmpWebServer.close();
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);
} }
}); });
} }

View 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
View File

@ -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();

View 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
View 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);
});
});
});

View File

@ -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
};

View File

@ -1,3 +0,0 @@
.test {
display: block;
}

Binary file not shown.

View File

@ -1,4 +0,0 @@
function test() {
console.info('This is nothing but a js file, to test the js download');
}
test();

View File

@ -1,3 +0,0 @@
{
'testkey': 'this is just a normal json file'
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View File

@ -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="&#xe800;" 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="&#xe801;" 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
View File

@ -0,0 +1,3 @@
module.exports = {
foo: 'bar',
};

1
test/fixtures/upload.txt vendored Normal file
View File

@ -0,0 +1 @@
1

View File

@ -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
}

View File

@ -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);

View 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();
});
});
});
});

View File

@ -1,34 +1,25 @@
/*
* test for rule replaceOption rule
*
*/
const ruleLoader = require('../../lib/ruleLoader'); const ruleLoader = require('../../lib/ruleLoader');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const localModulePath = path.join(__dirname, '../util/CommonUtil.js'); const localModulePath = path.join(__dirname, '../fixtures/someRule.js');
describe('rule loader', () => { describe('ruleLoader', () => {
it('should successfully cache a remote file', done => { it('should successfully cache a remote file', async () => {
ruleLoader.cacheRemoteFile('https://cdn.bootcss.com/lodash.js/4.16.4/lodash.min.js') await ruleLoader.cacheRemoteFile('https://cdn.bootcss.com/lodash.js/4.16.4/lodash.min.js')
.then(filePath => { .then(filePath => {
let content; let content;
if (filePath) { if (filePath) {
content = fs.readFileSync(filePath, { encoding: 'utf8' }); content = fs.readFileSync(filePath, { encoding: 'utf8' });
} }
expect(content && content.length > 100).toBe(true); expect(content && content.length > 100).toBe(true);
done(); });
})
.catch(done.fail);
}); });
it('should load a local module ../util/CommonUtil', done => { it('should load a local module ../util/CommonUtil', async () => {
ruleLoader.loadLocalPath(localModulePath) await ruleLoader.loadLocalPath(localModulePath)
.then(module => { .then(module => {
expect(module.printLog).not.toBeUndefined(); expect(module.foo).not.toBeUndefined();
done(); });
})
.catch(done.fail);
}); });
it('should smart load a remote module', done => { it('should smart load a remote module', done => {
@ -43,7 +34,7 @@ describe('rule loader', () => {
it('should smart load a local module', done => { it('should smart load a local module', done => {
ruleLoader.requireModule(localModulePath) ruleLoader.requireModule(localModulePath)
.then(module => { .then(module => {
expect(module.printLog).not.toBeUndefined(); expect(module.foo).not.toBeUndefined();
done(); done();
}) })
.catch(done.fail); .catch(done.fail);

View File

@ -1,17 +1,13 @@
/*
* test for rule replaceOption rule
*
*/
const util = require('../../lib/util'); const util = require('../../lib/util');
describe('utils', () => { describe('utils', () => {
it('should get some free ports', done => { it('getFreePort', async () => {
const count = 100; const count = 100;
const tasks = []; const tasks = [];
for (let i = 1; i <= count; i++) { for (let i = 1; i <= count; i++) {
tasks.push(util.getFreePort()); tasks.push(util.getFreePort());
} }
Promise.all(tasks) await Promise.all(tasks)
.then((results) => { .then((results) => {
// ensure ports are unique // ensure ports are unique
const portMap = {}; const portMap = {};
@ -19,8 +15,6 @@ describe('utils', () => {
portMap[portNumber] = true; portMap[portNumber] = true;
}); });
expect(Object.keys(portMap).length).toEqual(count); expect(Object.keys(portMap).length).toEqual(count);
done(); });
})
.catch(done.fail);
}); });
}); });

View File

@ -1 +0,0 @@
* this is a folder to save test reports *

View 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);
});
});
});

View 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);
});
});
});

View 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
View 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);
});
});

View File

@ -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;

View File

@ -1,3 +0,0 @@
const Server = require('./server.js');
new Server();

View File

@ -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)
});
});

View File

@ -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);
});

View File

@ -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();
});
}
});
});

View File

@ -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');
});
}
});
});
}

View File

@ -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();
}
}
});
});
}

View File

@ -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
};
}
}
};

View File

@ -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
}
}
}
};

View File

@ -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;
}
};

View File

@ -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
};
}
}
};

View File

@ -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;
// }
};

View File

@ -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
};
}
},
};

View File

@ -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;
}
};

View File

@ -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
}
};
}
}
};

View File

@ -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();
});
});
});
}

View File

@ -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');
});
});
});
}

View File

@ -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');
});
});
});
}

View File

@ -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');
});
});
});
}

View File

@ -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');
});
});
});
}

View File

@ -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');
});
});
});
}

View File

@ -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');
});
});
});
}

View File

@ -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');
});
});
});
}

View File

@ -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
View 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,
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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
};

View File

50
test/web/curlUtil.spec.js Normal file
View 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);
});
});

View 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
View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -1,7 +1,7 @@
/** /**
* AJAX操作工具类 * AJAX操作工具类
*/ */
import PromiseUtil from './PromiseUtil'; import PromiseUtil from './promiseUtil';
export function getJSON(url, data) { export function getJSON(url, data) {
const d = PromiseUtil.defer(); const d = PromiseUtil.defer();
fetch(url + serializeQuery(data)) fetch(url + serializeQuery(data))
@ -53,10 +53,10 @@ export function isApiSuccess (response) {
return response.status === 'success'; return response.status === 'success';
} }
const ApiUtil = { const apiUtil = {
getJSON, getJSON,
postJSON, postJSON,
isApiSuccess isApiSuccess
}; };
export default ApiUtil; export default apiUtil;

View File

@ -1,5 +1,5 @@
/** /**
* define all Constant variables here * define all constant variables here
*/ */
module.exports.MenuKeyMap = { module.exports.MenuKeyMap = {

View File

@ -15,7 +15,7 @@ import { message } from 'antd';
*/ */
export function initWs(wsPort = location.port, path = 'do-not-proxy') { export function initWs(wsPort = location.port, path = 'do-not-proxy') {
if(!WebSocket){ 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}`); 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 { export default {
initWs: initWs initWs: initWs
}; };

62
web/src/common/apiUtil.js Normal file
View 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