├── .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 | --------------------------------------------------------------------------------