├── .gitignore
├── README.md
├── auths
├── hi.js
└── uuap.js
├── bin
└── bird
├── devtools
├── bird-extend-script.js
└── change-user-script.js
├── index.js
├── lib
├── change-user.js
├── mock-cache.js
├── mock.js
├── proxy.js
├── static.js
└── unused
│ ├── logout.js
│ └── timeout.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_store
3 | .idea/
4 | demo/
5 | birdfile.js
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # This is bird with auth
2 |
3 | ## usage
4 |
5 | ### global
6 |
7 | - npm install -g birdv2
8 | - touch birdfile.js
9 | - vi birdfile.js
10 |
11 | ```
12 | // sample birdfile.js
13 | module.exports = {
14 | name: 'ar',
15 | bird_port: 7676,
16 | staticFileRootDirPath: '../webapp',
17 | server: 'http://xx.xx.com:8901/ar/',
18 | uuap_server: 'http://xx.xx.com:8100',
19 | username: 'jay',
20 | password_suffix: ''
21 | };
22 | ```
23 | - bird `or` bird -c ../yourPath/birdfile.js
24 |
25 | ### local
26 |
27 | - npm install -D birdv2
28 | - touch birdfile.js
29 | - vi birdfile.js
30 |
31 | ```
32 | // sample birdfile.js
33 | var config = {
34 | name: 'ar',
35 | bird_port: 7676,
36 | staticFileRootDirPath: '../webapp',
37 | server: 'http://xx.xx.com:8901/ar/',
38 | uuap_server: 'http://xx.xx.com:8100',
39 | username: 'jay',
40 | password_suffix: ''
41 | };
42 | require('bird')(config)
43 | ```
44 |
45 | - node birdfile.js
46 |
47 | ### as middleware with mock
48 |
49 | 请参考finfa
50 |
51 | ## config
52 | - bird的配制可是一个object,也可以是一个array, eg:
53 | ```
54 | var config = {
55 | name: 'ar',
56 | bird_port: 7676,
57 | staticFileRootDirPath: '../webapp',
58 | server: 'http://xx.xx.com:8901/ar/',
59 | uuap_server: 'http://xx.xx.com:8100',
60 | username: 'jay',
61 | password_suffix: ''
62 | };
63 |
64 | `or`
65 | var config = [{
66 | name: 'ar',
67 | bird_port: 7676,
68 | staticFileRootDirPath: '../webapp',
69 | server: 'http://xx.xx.com:8901/ar/',
70 | uuap_server: 'http://xx.xx.com:8100',
71 | username: 'jay',
72 | password_suffix: ''
73 | }, {
74 | name: 'ar2',
75 | bird_port: 7777,
76 | staticFileRootDirPath: '../webapp',
77 | server: 'http://xx.xx.com:8902/ar/',
78 | uuap_server: 'http://xx.xx.com:82S00',
79 | username: 'jay',
80 | password_suffix: ''
81 | }]
82 | ```
83 | - 下面是详细的配制说明,*表示必须的配制, #表示正在开发或功能不稳定的配制, 其他是可选项
84 | ```
85 | // *服务名字,本配制以ar为例
86 | name: 'ar',
87 | // *服务端口
88 | bird_port: 3000,
89 | // *静态文件目录,可以为相对路径,如:../build
90 | staticFileRootDirPath: '/home/zp/work/ar/src/main/webapp/resources',
91 | // *测试机地址,是否带`ar`看环境的context
92 | server: 'http://xx-xx.epc:8901/ar/',
93 | // *该测试机对应的uuap地址
94 | uuap_server: 'http://uuap_test.com:8100',
95 | // *你想用谁登录
96 | username: 'who_you_want',
97 | // *密码后缀,没有就留空
98 | password_suffix: '',
99 | // 是否开启dev-tools(提供切换用户等功能)default:false
100 | dev_tool: {
101 | type: 'input', // #暂时只有这一个,后续加select
102 | top: 20, // 工具上边距
103 | right: 20 //右边距
104 | },
105 | // feapps专用登录,hard code this one. default:false
106 | bprouting: 'bprouting/BpFlowRouting?appindex=true',
107 | // 是否使用静态的cookie,录bird出问题了你还可以把cookie粘到这里,像旧版一样default:false
108 | cookie: 'sessionid=XXXXXXXXXXX',
109 | // 转发路由,你可以将本地的请求转发到指定的路径
110 | router: {
111 | '/ar': '/ar-web' // 将http://xx-xx.epc:8901/ar/XX/XX -> http://xx-xx.epc:8901/ar-web/XX/XX
112 | },
113 | // 登录方式,默认是使用uuap来登录,加载auths/uuap.js default:'uuap'
114 | auth_standalone: 'uuap',
115 | // #当cookie效了重新cookie,当然,你可以重启bird来手动获取.default:true
116 | keep_alive: true,
117 | // #使用本地的数据,不转发. 当服务器当了,你可以造些假数据来本地测试
118 | use_local_data: {
119 | '/ar': '/your/data/path'
120 | }
121 | // #mock配置
122 | mock: {
123 | path: '../mock', // 相对于staticFileRootDirPath的路径,用于存放mock文件的文件夹
124 | map: {
125 | '/test/test': 'test' // key为请求路径,value为映射的js文件
126 | }
127 | }
128 | // #mock缓存功能配置, 需要在配置好mock才可用
129 | mock_cache: __dirname, // 设置为 __dirname则爬取接口数据存放到mock文件夹中且更新birdfile.js中的mock.map
130 | ```
131 | ## extendable
132 |
133 | - 如果你的项目不是用uuap登录的,那你需要配制auth_standalone选项, 然后在auths/目录下添加上对应的js文件,当然你可以联系我,告诉你项目的地址 及登录帐号, 让我帮你加
134 |
135 |
136 |
137 | ## browser ci api
138 |
139 | - 打开浏览器的console,可以执行以下命令:
140 | - birdv2.use('uuap_name') 切换到其他用户登录
141 |
142 | ## notice
143 |
144 | - 当获取的cookie失效时,如果response是重定向到其他url,bird会自动获取新cookie,并重定向回来,用户是感觉不到这个过程的。如果response是返回一个对象,使浏览器登出,跳转到 /logout,bird会根据request->header->referer又跳转回来,因为referer不包括锚点信息,对于angular用户会发现页面跳到默认页面;同理,手动点登出也会跳回来,只是换了个cookie
145 |
146 |
147 | ## todo
148 |
149 | - track forward history??
150 | - forwarding local json data
151 |
--------------------------------------------------------------------------------
/auths/hi.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var url = require('url')
3 | var path = require('path')
4 | var request = require('request')
5 | var colors = require('colors');
6 | var Promise = require('bluebird');
7 | /*
8 | hi的必要配制
9 | name: 'hi xx',
10 | bird_port: 7676,
11 | staticFileRootDirPath: '../any',
12 | auth_standalone: true,
13 | server: 'http://10.10.88.88:8888/',
14 | auth_url: 'http://10.10.88.88:8888/hi/api/login/loginCS',
15 | username: 'admin',
16 | password_suffix: ''
17 |
18 | */
19 | module.exports = function (config, jar) {
20 | var promise = new Promise(function hiLogin(resolve) {
21 | var AUTH_URL = config.auth_url;
22 | var HEADERS = config.auth_server_headers || {'Content-Type': 'application/json'};
23 | //保证路径完整
24 | var TARGET_SERVER = config.server.slice('-1') === '/' ? config.server : config.server + '/';
25 | var METHOD = config.method || 'POST';
26 | var USERNAME = config.username;
27 | var PASSWORD_SUFFIX = config.password_suffix;
28 | var PASSWORD = config.password;
29 | // clearTargetServerJar(jar, TARGET_SERVER, UUAP_SERVER);
30 | request({
31 | method: METHOD,
32 | url: AUTH_URL,
33 | json: true,
34 | headers: HEADERS,
35 | body: {
36 | loginName: USERNAME,
37 | password: PASSWORD
38 | },
39 | jar: jar
40 | }, function(error, response, body) {
41 | console.log(error,body);
42 | resolve();
43 | });
44 | });
45 | return promise;
46 | }
47 |
48 | function clearTargetServerJar (jar, serverUrl, uuapUrl) {
49 | try {
50 | jar._jar.store.idx[url.parse(serverUrl).hostname] = undefined;
51 | } catch (e) {
52 | console.log(e)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/auths/uuap.js:
--------------------------------------------------------------------------------
1 | var express = require('express')
2 | var fs = require('fs')
3 | var url = require('url')
4 | var path = require('path')
5 | var request = require('request')
6 | // request.debug = true;
7 | var colors = require('colors');
8 | // var http = require('http');
9 | var cheerio = require('cheerio')
10 | var Promise = require('bluebird');
11 |
12 | process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
13 |
14 | /*
15 | uuap--默认的登录方式
16 | */
17 | module.exports = function (config, jar) {
18 | var promise = new Promise(function uuapLogin(resolve) {
19 | var UUAP_SERVER = config.uuap_server;
20 | //保证路径完整
21 | var TARGET_SERVER = config.server.slice('-1') === '/' ? config.server : config.server + '/';
22 | var PASSWORD_SUFFIX = config.password_suffix;
23 | var USERNAME = config.username;
24 | clearTargetServerJar(jar, TARGET_SERVER, UUAP_SERVER);
25 | // request and login in uuap
26 | request({
27 | url: UUAP_SERVER,
28 | jar: jar
29 | }, function(error, response, body) {
30 |
31 | // use cheerio to parse dom
32 | var $ = cheerio.load(body);
33 | var hiddenInputs = $('#fm1 input[type=hidden]');
34 | var lt = $(hiddenInputs.get(0)).val();
35 | var execution = $(hiddenInputs.get(1)).val();
36 | var type = 1;
37 | var _eventId = 'submit';
38 | var rememberMe = 'on';
39 | var username = USERNAME;
40 | var password = USERNAME + (PASSWORD_SUFFIX || '');
41 |
42 | // mimic uuap login
43 | request.post({
44 | url: UUAP_SERVER + '/login',
45 | form: {
46 | username: username,
47 | password: password,
48 | rememberMe: rememberMe,
49 | _eventId: _eventId,
50 | type: type,
51 | execution: execution,
52 | lt: lt
53 | },
54 | jar: jar
55 | }, function(err, httpResponse, body) {
56 | // http://stackoverflow.com/questions/20082893/unable-to-verify-leaf-signature
57 | process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
58 | // request the logined uuap again, and let it redirect for us
59 | // erp feapps need addition routing policy...
60 | var toUrl = UUAP_SERVER + '/login?service=' + encodeURIComponent(TARGET_SERVER)
61 | if (config.bprouting) {
62 | var bproutingUrl = TARGET_SERVER + (config.bprouting || + '')
63 | toUrl = UUAP_SERVER + '/login?service=' + encodeURIComponent(bproutingUrl)
64 | }
65 | request({
66 | url: toUrl,
67 | jar: jar
68 | }, function(error, response, body) {
69 | // do nothing, jar record the session automatically
70 | // var cookies = jar.getCookies(TARGET_SERVER);
71 | console.log(error, jar, '==============');
72 | resolve()
73 | })
74 | })
75 |
76 |
77 | });
78 | });
79 | return promise;
80 |
81 | }
82 |
83 | function clearTargetServerJar (jar, serverUrl, uuapUrl) {
84 | try {
85 | jar._jar.store.idx[url.parse(serverUrl).hostname] = undefined;
86 | jar._jar.store.idx[url.parse(uuapUrl).hostname] = undefined;
87 | // jar._jar.store.removeCookies('cq01-ite-ar-test01.epc', '/ar');
88 | } catch (e) {
89 | console.log(e)
90 | }
91 | }
--------------------------------------------------------------------------------
/bin/bird:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var program = require('commander');
4 | var bird = require('../index')
5 | var path = require('path')
6 | var configFileName = 'birdfile.js'
7 | program
8 | .version('0.0.3')
9 | .option('-c, --config', 'specify config file')
10 | .action(function (name) {
11 | if (!name) {
12 | // console.log('config filename must not be empty')
13 | return;
14 | }
15 | configFileName = name;
16 | })
17 |
18 | program.on('--help', function() {
19 | console.log(' Examples:');
20 | console.log('');
21 | console.log(' $ bird --help');
22 | console.log(' $ bird -h');
23 | console.log('');
24 | });
25 |
26 |
27 | program.parse(process.argv);
28 |
29 |
30 | var config;
31 | try {
32 | config = require(path.join(process.cwd(), configFileName))
33 | } catch (e){
34 | console.error('ERROR: please specify the correct bird file name')
35 | }
36 |
37 | if (config) {
38 | bird(config)
39 | }
--------------------------------------------------------------------------------
/devtools/bird-extend-script.js:
--------------------------------------------------------------------------------
1 | (function (document, window) {
2 | window.birdv2 = {
3 | config: {},
4 | use: function(username) {
5 | var xhttp = new XMLHttpRequest();
6 | xhttp.onload = function (e) {
7 | if (xhttp.status === 200) {
8 | window.location.reload();
9 | } else {
10 | console.log(this.config);
11 | }
12 | };
13 | xhttp.open('GET', 'bbbbiiiirrrrdddd' + '?username=' + username);
14 | xhttp.send();
15 | }
16 | }
17 |
18 | })(document, window)
19 |
20 |
--------------------------------------------------------------------------------
/devtools/change-user-script.js:
--------------------------------------------------------------------------------
1 | (function (document, window) {
2 | var config = window.birdv2.config.dev_tool
3 | var defaultConfig = {type:'input', top: 20, right: 20, width: 80, height: 30}
4 | var div = document.createElement('div');
5 | div.style.position = 'fixed'
6 | div.style.top = (config.top || defaultConfig.top) + 'px'
7 | div.style.right = (config.right || defaultConfig.right) + 'px'
8 | div.style.width = (config.width || defaultConfig.width) + 'px'
9 | div.style.height = (config.height || defaultConfig.height) + 'px'
10 | div.style['z-index'] = 999999;
11 | setTimeout(function () {
12 | document.body.appendChild(div)
13 | }, 2000)
14 | var input = document.createElement('input')
15 | input.id = 'birdv2-tool'
16 | input.style.width = '80px'
17 | input.setAttribute('placeholder', 'change user')
18 | var button = document.createElement('button')
19 | button.innerHTML = 'Go & Reload!'
20 | button.onclick = function (e) {
21 | if (!input.value.trim().length) return;
22 | var xhttp = new XMLHttpRequest();
23 | xhttp.onload = function (e) {
24 | if (xhttp.status === 200) {
25 | window.location.reload();
26 | }
27 | };
28 | xhttp.open('GET', 'bbbbiiiirrrrdddd' + '?username=' + input.value.trim());
29 | //xhttp.responseType = 'json';
30 | xhttp.send();
31 |
32 | }
33 | div.appendChild(input)
34 | div.appendChild(button)
35 |
36 | })(document, window)
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express')
2 | var request = require('request')
3 | var colors = require('colors');
4 |
5 | /**
6 | * start bird with config
7 | * @param {Object} config bird-configuration
8 | * @return {undefined}
9 | */
10 | module.exports = function start(config) {
11 | Object.assign = copyObj();
12 | // allow multiple bird instances
13 | if (config && Array.isArray(config)) {
14 | for (var i = 0; i < config.length; i++) {
15 | start(config[i])
16 | }
17 | return;
18 | }
19 |
20 | var jar = request.jar(); // jar to store cookies
21 | config = configResolver(config, jar);
22 |
23 | // check if config ok
24 | if (!config.ifOk) {
25 | console.info('check your configuration, pls')
26 | return;
27 | }
28 | // silence when not debugging
29 | if (!config.debug) {
30 | global.console.log = function (){};
31 | }
32 |
33 | if (config.middleware) {
34 | if (config.ifAuth) {
35 | auth(config, jar);
36 | }
37 | // http://stackoverflow.com/questions/20274483/how-do-i-combine-connect-middleware-into-one-middleware
38 | return listAll([
39 | require('./lib/mock')(config),
40 | require('./lib/change-user')(config),
41 | require('./lib/proxy')(config)
42 | ]);
43 | } else {
44 | if (config.ifAuth) {
45 | config.auth(config, jar).then(function () {
46 | // setup bird app
47 | var app = new express()
48 | app.all('*', require('./lib/mock')(config))
49 | app.all('*', require('./lib/static')(config))
50 | app.all('*', require('./lib/change-user')(config))
51 | if (config.ifProxy) {
52 | app.all('*', require('./lib/proxy')(config))
53 | }
54 | // go!
55 | app.listen(config.birdPort)
56 | console.info('BIRD'.rainbow, '============', config.name || '', 'RUNNING at', 'http://localhost:' + config.birdPort, '===============', 'BIRD'.rainbow);
57 | })
58 | } else {
59 | var app = new express()
60 | app.all('*', require('./lib/mock')(config))
61 | app.all('*', require('./lib/static')(config))
62 | if (config.ifProxy) {
63 | app.all('*', require('./lib/proxy')(config))
64 | }
65 | app.listen(config.birdPort)
66 | console.info('BIRD'.rainbow, '============', config.name || '', 'RUNNING at', 'http://localhost:' + config.birdPort, '===============', 'BIRD'.rainbow);
67 | }
68 | }
69 | }
70 |
71 | function configResolver (originConfig, jar) {
72 | if (!originConfig || typeof originConfig !== 'object') {
73 | return {
74 | ifOk: false
75 | }
76 | }
77 | var config = Object.assign({}, originConfig);
78 | if (!config.staticFileRootDirPath) {
79 | config.ifOk = false;
80 | return config
81 | }
82 | config.ifOk = true;
83 | if (config.server) {
84 | config.ifProxy = true;
85 | }
86 | if (config.username && (config.hasOwnProperty('password') || config.hasOwnProperty('password_suffix'))) {
87 | config.ifAuth = true;
88 | config.auth = require('./auths/' + (originConfig.authType || originConfig.auth_standalone || 'uuap'));
89 | }
90 | config.birdPort = originConfig.bird_port || 8888;
91 | config.jar = jar;
92 | return config;
93 | }
94 |
95 | function listAll(list) {
96 | return function (req, res, next) {
97 | (function iter(i) {
98 | var mid = list[i]
99 | if (!mid) return next()
100 | mid(req, res, function (err) {
101 | if (err) return next(err)
102 | iter(i + 1)
103 | })
104 | }(0))
105 | }
106 | }
107 |
108 | function copyObj(target, source) {
109 | if (Object.assign && typeof Object.assign === 'function' && !Object.assign.toString().match('copyObj')) {
110 | return Object.assign;
111 | } else {
112 | return assign;
113 | }
114 | function assign(target, source) {
115 | for (var i in source) {
116 | target[i] = source[i];
117 | if (typeof source[i] === 'object') {
118 | assign(target[i], source[i]);
119 | }
120 | }
121 | return target;
122 | }
123 | }
--------------------------------------------------------------------------------
/lib/change-user.js:
--------------------------------------------------------------------------------
1 |
2 | var config;
3 | var url = require('url');
4 | var BIRD_CHANGE_USER_PATHNAME = '/bbbbiiiirrrrdddd'
5 | var request = require('request');
6 |
7 | function changeUser(req, res, next) {
8 | var urlParsed = url.parse(req.url);
9 | if (urlParsed.pathname === BIRD_CHANGE_USER_PATHNAME) {
10 | var username = urlParsed.query.split('=')[1]
11 | config.username = username;
12 | config.jar = request.jar();
13 | config.auth(config, config.jar).then(function () {
14 | console.info(config.server + ' user switched to ', username.black.bgWhite)
15 | res.write('changed')
16 | res.end();
17 | })
18 | } else {
19 | next()
20 | }
21 | }
22 |
23 | module.exports = function (_config) {
24 | config = _config;
25 | return changeUser;
26 | }
--------------------------------------------------------------------------------
/lib/mock-cache.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var request = require('request');
4 |
5 | /**
6 | *
7 | * 这坨代码用来防止交叉写文件
8 | */
9 | var cbs = [];
10 | var process = true;
11 | function processStack(){
12 | if (process) {
13 | process = false;
14 | var first = cbs.shift();
15 | if (first && typeof first === 'function') {
16 | first(function () {
17 | processStack();
18 | process = true;
19 | });
20 | }
21 | }
22 |
23 |
24 | }
25 | /**
26 | *
27 | * mock 缓存功能,在配置中打开mock_cache设置为__dirname,
28 | * 则在转发接口时将接口数据缓存mock文件夹中,并且重写birdfile.js
29 | * @param config
30 | * @param url
31 | * @param filePath
32 | * @returns {boolean}
33 | */
34 | function mock_cache (config, url, filePath) {
35 | if (filePath.match('.html')) {
36 | return false;
37 | }
38 | var reWriteBirdFile;
39 | var cache_filePath = filePath.replace(config.staticFileRootDirPath, '');
40 | var cache_fileName = cache_filePath.replace(/\//g, '_').substr(1);
41 | var cache_path = path.join(config.staticFileRootDirPath, config.mock.path, cache_fileName + '.js');
42 | cbs.push(function (callback) {
43 | fs.readFile(config.mock_cache + '/birdfile.js', 'utf-8', function (err,data) {
44 | if (!data) {
45 | console.info('warn: 操作太频繁!');
46 | return false;
47 | }
48 | var mapStringIndex = data.indexOf('map');
49 | var mapData = data.substring(data.indexOf('{', mapStringIndex), data.indexOf('}', mapStringIndex) + 1)
50 | if (!mapData.match(cache_fileName)) {
51 | var newMapData = mapData.replace('}',"// '" + cache_filePath + "': '" + cache_fileName + "',\r\n\t}");
52 | reWriteBirdFile = data.replace(mapData, newMapData);
53 | fs.writeFile(config.mock_cache + '/birdfile.js', reWriteBirdFile, [{encoding: 'utf8'}], function (){
54 |
55 | callback();
56 | })
57 | } else {
58 | callback();
59 | }
60 | });
61 | });
62 | processStack();
63 | request({
64 | url: url,
65 | jar: config.jar,
66 | headers: {
67 | 'cookie': config.cookie
68 | }
69 | },function(error, response, body) {
70 | var contentType = response.headers['content-type'];
71 | if (contentType.match('json')) {
72 | var data = 'module.exports=' + JSON.stringify(JSON.parse(body), null, 4);
73 | fs.writeFile(cache_path, data, [{encoding: 'utf8'}], function (){
74 |
75 | })
76 | }
77 |
78 | })
79 | };
80 | module.exports = mock_cache;
--------------------------------------------------------------------------------
/lib/mock.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var config;
3 | var url = require('url');
4 |
5 | function mockHandle(req, res, next) {
6 | var urlParsed = url.parse(req.url);
7 | var mock = config.mock;
8 | var mockFile = mock && mock.map && mock.map[urlParsed.pathname]
9 | if (mockFile) {
10 | var mockFilePath = path.join(config.staticFileRootDirPath, config.mock.path, mockFile);
11 | var ret = require(mockFilePath);
12 | res.json(ret);
13 | } else {
14 | next()
15 | }
16 | }
17 |
18 | module.exports = function (_config) {
19 | config = _config;
20 | return mockHandle;
21 | }
--------------------------------------------------------------------------------
/lib/proxy.js:
--------------------------------------------------------------------------------
1 | var config;
2 | var http = require('http-debug').http;
3 | var https = require('http-debug').https;
4 | var fs = require('fs')
5 | var url = require('url')
6 | var path = require('path')
7 | var mock_cache = require('./mock-cache')
8 |
9 | function proxy (req, res, next) {
10 | var ROUTER = config.router;
11 | var COOKIE = config.cookie;
12 | // var AUTO_INDEX = config.autoIndex ? config.autoIndex.split(/\x20+/) : ['index.html']
13 | //保证路径完整
14 | var TARGET_SERVER = config.server.slice('-1') === '/' ? config.server : config.server + '/';
15 | var urlParsed = url.parse(req.url);
16 | var filePath = resolveFilePath(config.staticFileRootDirPath, urlParsed.pathname);
17 | if (true) {
18 | // set up forward request
19 | var headers = req.headers;
20 | headers.cookie = COOKIE || redeemCookieFromJar(config.jar.getCookies(TARGET_SERVER));
21 | // headers.host = config.host;
22 | // console.info("headers.cookie", headers.cookie)
23 | delete headers['x-requested-with'];
24 | var requestPath = router(urlParsed.path, ROUTER);
25 | // console.info('requestPath:', requestPath);
26 | var urlOptions = {
27 | host: url.parse(TARGET_SERVER).hostname,
28 | port: url.parse(TARGET_SERVER).port,
29 | path: requestPath,
30 | method: req.method,
31 | headers: headers,
32 | rejectUnauthorized: false
33 | };
34 | console.log('headers', headers);
35 | // proxy to target server
36 | var forwardUrl = url.resolve(TARGET_SERVER, requestPath);
37 | // log forwarding message
38 | console.info('fowarding', filePath.red, 'to', forwardUrl.cyan);
39 | var httpOrHttps = url.parse(TARGET_SERVER).protocol === 'http:' ? http : https;
40 | var forwardRequest = httpOrHttps.request(urlOptions, function(response) {
41 | // set headers to the headers in origin request
42 | res.writeHead(response.statusCode, response.headers);
43 | response.on('data', function(chunk) {
44 | // body += chunk;
45 | res.write(chunk);
46 | });
47 |
48 | response.on('end', function() {
49 | // console.info(body)
50 | res.end();
51 | });
52 | });
53 | if(config.mock_cache && typeof config.mock === 'object' && typeof config.mock.map === 'object') {
54 | mock_cache(config, forwardUrl, filePath);
55 | }
56 |
57 | if (req._body) {
58 | forwardRequest.write(JSON.stringify(req.body));
59 | }
60 |
61 | forwardRequest.on('error', function(e) {
62 | console.error('problem with request: ' + e.message);
63 | });
64 |
65 | req.addListener('data', function(chunk) {
66 | forwardRequest.write(chunk);
67 | });
68 |
69 | req.addListener('end', function() {
70 | forwardRequest.end();
71 | });
72 | }
73 | }
74 |
75 | module.exports = function (_config) {
76 | config = _config;
77 | return proxy;
78 | }
79 |
80 | /**
81 | * resolve static file path,, take care of welcome file
82 | * @param {String} staticFileRootDirPath
83 | * @param {String} pathname
84 | * @return {String} working path
85 | */
86 | function resolveFilePath (staticFileRootDirPath, pathname) {
87 | if (pathname === '/') { // resolve welcome file
88 | return path.join(staticFileRootDirPath, 'index.html')
89 | }
90 | return path.join(staticFileRootDirPath, pathname);
91 | }
92 |
93 | /**
94 | * get the normalized cookie from jar
95 | * @param {Array} cookieArray
96 | * @return {String} cookie string used in headers
97 | */
98 | function redeemCookieFromJar (cookieArray) {
99 | var result = '';
100 | for (var i = 0; i < cookieArray.length; i++) {
101 | result += cookieArray[i].key + '=' + cookieArray[i].value + ';';
102 | if (i !== cookieArray.length - 1) {
103 | result += ' ';
104 | }
105 | }
106 | return result;
107 | }
108 |
109 | function router (url, router) {
110 | var path = '';
111 | var reg;
112 | if (router) {
113 | for (var i in router) {
114 | reg = new RegExp(i)
115 | if (reg.test(url)) {
116 | path = url.replace(reg, router[i]);
117 | console.log('special route mapping found! converting...', url, 'to', path)
118 | }
119 | }
120 | } else {
121 | path = url;
122 | }
123 | return path;
124 | }
125 |
--------------------------------------------------------------------------------
/lib/static.js:
--------------------------------------------------------------------------------
1 | var config;
2 | var url = require('url');
3 | var path = require('path')
4 | var fs = require('fs');
5 | var mime = require('mime-types');
6 | var cheerio = require('cheerio')
7 | var BIRD_USER_SCRIPT = fs.readFileSync(path.join(__dirname, '../devtools/change-user-script.js'), 'utf8');
8 | var BIRD_EXTEND_SCRIPT = fs.readFileSync(path.join(__dirname, '../devtools/bird-extend-script.js'), 'utf8');
9 |
10 | function staticHandle(req, res, next) {
11 | var urlParsed = url.parse(req.url);
12 | var filePath = path.join(config.staticFileRootDirPath, urlParsed.pathname === '/' ? 'index.html' : urlParsed.pathname)
13 | fs.stat(filePath, function (err, stats) {
14 | if (err) {
15 | next()
16 | } else if (stats.isFile() /* file */) {
17 | // server as static file
18 | fs.readFile(filePath, function(err, buffer) {
19 | if (isHtmlPage(buffer)) {
20 | // add something nasty in it, that's where bird dev-tool exists
21 | var $ = cheerio.load(buffer.toString('utf-8'));
22 | $('head').append('')
23 | var o = Object.assign({}, config);
24 | o.jar = undefined;
25 | $('head').append('')
26 | if (config.dev_tool) {
27 | $('head').append('')
28 | // console.log($.html())
29 | }
30 | res.setHeader('Content-Type', mime.lookup('.html'));
31 | res.write($.html())
32 | res.end();
33 | } else {
34 | var mimeType = mime.lookup(path.extname(filePath));
35 | res.setHeader('Content-Type', mimeType);
36 | res.write(buffer);
37 | res.end();
38 | }
39 | })
40 | } else if (stats.isDirectory() /* directory */) {
41 | var AUTO_INDEX;
42 | if (Array.isArray(config.autoIndex)) {
43 | AUTO_INDEX = config.autoIndex
44 | } else {
45 | AUTO_INDEX = config.autoIndex ? config.autoIndex.split(/\x20+/) : ['index.html']
46 | }
47 | if (AUTO_INDEX) {
48 | var fp;
49 | for (var i = 0; i < AUTO_INDEX.length; i++) {
50 | fp = path.join(filePath, AUTO_INDEX[i]);
51 | if (isFile(fp)) {
52 | filePath = fp;
53 | fs.readFile(filePath, function(err, buffer) {
54 | var mimeType = mime.lookup(path.extname(filePath));
55 | res.setHeader('Content-Type', mimeType);
56 | res.write(buffer);
57 | res.end();
58 | });
59 | break;
60 | }
61 | }
62 | // if (!isFile(filePath)) {
63 | // emptyPage(res, filePath);
64 | // }
65 | }
66 | } else {
67 | // shit happens
68 | next()
69 | }
70 | })
71 | }
72 |
73 | module.exports = function (_config) {
74 | config = _config;
75 | return staticHandle;
76 | }
77 |
78 | function exists(p) {
79 | return fs.existsSync(p);
80 | }
81 |
82 | function isFile(p) {
83 | return exists(p) && fs.statSync(p).isFile();
84 | }
85 |
86 | function isDir(p) {
87 | return exists(p) && fs.statSync(p).isDirectory();
88 | }
89 |
90 | function emptyPage(res, fp) {
91 | console.info('Local', fp.red, 'not exists'.cyan);
92 | res.status(404).send('Not found' + (fp ? ' "' + fp + '"' : '') + '!');
93 | res.end();
94 | }
95 |
96 | function isHtmlPage (buffer) {
97 | var temp = buffer.slice(0, 20);
98 | var reg = new RegExp('', 'i')
99 | return reg.test(temp.toString('utf-8'))
100 | }
--------------------------------------------------------------------------------
/lib/unused/logout.js:
--------------------------------------------------------------------------------
1 |
2 | var config;
3 | var url = require('url');
4 |
5 | function logout(req, res, next) {
6 | var urlParsed = url.parse(req.url);
7 | if (urlParsed.pathname.match(/logout/)) {
8 | config.auth(config, config.jar).then(function () {
9 | console.info(config.server + ' cookie timeout and get a new ', jar.getCookies(config.server))
10 | res.writeHead(302, {location: req.headers.referer || 'http://' + req.headers.host});
11 | res.end();
12 | });
13 | } else {
14 | next()
15 | }
16 | }
17 |
18 | module.exports = function (_config) {
19 | config = _config;
20 | return logout;
21 | }
--------------------------------------------------------------------------------
/lib/unused/timeout.js:
--------------------------------------------------------------------------------
1 | //check if cookie is timeout
2 | // if (response.headers.location && response.headers.location.match(BIRD_LOGOUT_URL_REG)) {
3 | // birdAuth(config, jar, function () {
4 | // console.log(TARGET_SERVER + '302 cookie timeout and get a new ', jar.getCookies(TARGET_SERVER))
5 | // response.headers.location = urlParsed.path;
6 | // res.writeHead(response.statusCode, response.headers);
7 | // res.end();
8 | // });
9 | // } else{
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "birdv2",
3 | "version": "0.0.7",
4 | "description": "birdv2 is a proxy server with cas auth built in, originate from `bird`",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/chenchengxing/bird-v2.git"
12 | },
13 | "author": "",
14 | "license": "MIT",
15 | "bugs": {
16 | "url": "https://github.com/chenchengxing/bird-v2/issues"
17 | },
18 | "homepage": "https://github.com/chenchengxing/bird-v2",
19 | "dependencies": {
20 | "bluebird": "3.1.5",
21 | "cheerio": "0.19.0",
22 | "colors": "1.1.2",
23 | "commander": "^2.9.0",
24 | "express": "4.13.3",
25 | "http-debug": "^0.1.2",
26 | "mime-types": "2.1.8",
27 | "request": "2.67.0"
28 | },
29 | "devDependencies": {
30 | "mocha": "^2.1.0",
31 | "should": "^7.1.1",
32 | "q": "^1.4.1"
33 | },
34 | "bin": {
35 | "bird": "./bin/bird"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------