├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── lib ├── static.js └── util.js ├── package.json └── test ├── index.js └── static.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## ChangeLog for: http-route-proxy 2 | 3 | #### Version 0.2.0 - 2015/08/26 4 | - [Bug]: Fixed bug when using as express middleware sometimes base-url will be stripped off. 5 | 6 | #### Version 0.1.1 - 2013/12/30 7 | 8 | - [Feature]: Support using as express connect middleware 9 | 10 | #### Version 0.1.0 - 2013/12/28 11 | 12 | - [Feature]: Forward to various host on matching the path action. 13 | 14 | #### Version 0.0.6-2 - 2013/12/26 15 | 16 | - [Feature]: Support write protocal in hostname(https://xxxx.com) instead of using protocal(https: true) option. 17 | 18 | #### Version 0.0.6-1 - 2013/12/26 19 | 20 | - [Feature]: route field isn't necessary and it has defualt value of `['/']` 21 | 22 | #### Version 0.0.6 - 2013/12/26 23 | 24 | - [Feature]: Support set request and response headers 25 | 26 | #### Version 0.0.5-1 - 2013/12/26 27 | 28 | - [Fixed bug]: route proxy http to https website which certificate has not been unauthorized will be response error 29 | 30 | #### Version 0.0.5 - 2013/12/25 31 | 32 | - [Feature]: Support https route proxy 33 | 34 | #### Version 0.0.4 - 2013/12/18 35 | 36 | - [Feature]: Support cross-domain request 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 guankaishe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | http-route-proxy 2 | ========== 3 | 4 | Convenient HTTP/HTTPS route proxy for cross-domain request, request forward.Support using as express connect middleware. 5 | 6 | ### Installing 7 | 8 | ```bash 9 | npm install http-route-proxy 10 | ``` 11 | 12 | ### Testing 13 | 14 | ```bash 15 | npm test 16 | ``` 17 | 18 | ### Using http-route-proxy 19 | 20 | #### Sample: 21 | 22 | ```javascript 23 | var proxyServer = require('http-route-proxy'); 24 | 25 | /** 26 | * proxy configs 27 | */ 28 | proxyServer.proxy([ 29 | // common config 30 | { 31 | // origin host + port 32 | from: 'localhost:9000', 33 | // forward host + port 34 | to: '192.168.1.1:8088', 35 | // match forward action rule 36 | // `"/"` means forward match all actions, 37 | // `!/public` means match local static forward match `/public.*` 38 | route: ['/', '!/public'] 39 | } 40 | ]); 41 | ``` 42 | 43 | #### Set Headers 44 | ```javascript 45 | proxyServer.proxy([ 46 | { 47 | from: 'localhost:9000', 48 | to: 'www.google.com', 49 | https: true, 50 | headers: { 51 | req: {origin: 'www.google.com', referer: 'www.google.com'}, 52 | res: {'access-control-allow-origin': 'https://www.google.com', 'access-control-allow-credentials': true} 53 | } 54 | } 55 | ]); 56 | ``` 57 | 58 | #### Using HTTPS 59 | ```javascript 60 | proxyServer.proxy([ 61 | { 62 | from: 'localhost:9000', 63 | to: 'www.google.com', 64 | https: true 65 | } 66 | ]); 67 | // or 68 | proxyServer.proxy([ 69 | { 70 | from: 'localhost:9000', 71 | to: 'https://www.google.com' 72 | } 73 | ]); 74 | ``` 75 | 76 | #### Forward By Request Path 77 | Forward to various host on matching the path action 78 | 79 | ```javascript 80 | proxyServer.proxy([ 81 | { 82 | from: 'localhost:9000', 83 | to: 'www.google.com', 84 | route: [ 85 | { 86 | action: '/switer', 87 | forward: 'https://github.com', 88 | headers: { 89 | req: {origin: 'https://github.com'} 90 | } 91 | }, 92 | { 93 | action: '/imper', 94 | forward: 'https://switer.github.io', 95 | headers: { 96 | req: {origin: 'https://github.io'} 97 | } 98 | }, 99 | '!/public' 100 | ] 101 | } 102 | ]); 103 | ``` 104 | 105 | #### Using as express connect middleware 106 | __express app.js config:__ 107 | Push it on the first of connect 108 | 109 | ```javascript 110 | app.use(proxyServer.connect({ 111 | to: 'www.google.com', 112 | https: true, 113 | route: ['/'] 114 | })); 115 | app.use(express.favicon()); 116 | app.use(express.logger('dev')); 117 | app.use(express.bodyParser()); 118 | app.use(express.methodOverride()); 119 | app.use(app.router); 120 | app.use(express.static(path.join(__dirname, 'public'))); 121 | ``` 122 | 123 | ### Change Log 124 | 125 | #### Version 0.2.0 - 2015/08/26 126 | - [Bug]: Fixed bug when using as express middleware sometimes base-url will be stripped off. 127 | 128 | #### Version 0.1.1 - 2013/12/30 129 | 130 | - [Feature]: Support using as express connect middleware 131 | 132 | #### Version 0.1.0 - 2013/12/28 133 | 134 | - [Feature]: Forward to various host on matching the path action. 135 | 136 | #### Version 0.0.6-1 - 2013/12/26 137 | 138 | - [Feature]: route field isn't necessary and it has defualt value of `['/']` 139 | 140 | #### Version 0.0.6 - 2013/12/26 141 | 142 | - [Feature]: Support set request and response headers 143 | 144 | #### Version 0.0.5-1 - 2013/12/26 145 | 146 | - [Fixed bug]: route proxy http to https website which certificate has not been unauthorized will be response error 147 | 148 | #### Version 0.0.5 - 2013/12/25 149 | 150 | - [Feature]: Support https route proxy 151 | 152 | #### Version 0.0.4 - 2013/12/18 153 | 154 | - [Feature]: Support cross-domain request 155 | 156 | 157 | ### License 158 | 159 | The MIT License (MIT) 160 | 161 | Copyright (c) 2013 `guankaishe` 162 | 163 | Permission is hereby granted, free of charge, to any person obtaining a copy of 164 | this software and associated documentation files (the "Software"), to deal in 165 | the Software without restriction, including without limitation the rights to 166 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 167 | the Software, and to permit persons to whom the Software is furnished to do so, 168 | subject to the following conditions: 169 | 170 | The above copyright notice and this permission notice shall be included in all 171 | copies or substantial portions of the Software. 172 | 173 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 174 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 175 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 176 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 177 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 178 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 179 | 180 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http-route-proxy 3 | * http://github.com/switer/http-route-proxy 4 | * 5 | * Copyright (c) 2013 "switer" guankaishe 6 | * Licensed under the MIT license. 7 | * https://github.com/switer/http-route-proxy/blob/master/LICENSE 8 | */ 9 | 10 | var httpProxy = require('http-proxy'), // base on nodejitsu proxy server 11 | staticServer = require('./lib/static'), // static server 12 | util = require('./lib/util'), 13 | colors = require('colors'), // use to pretty console log 14 | path = require('path'), 15 | Url = require('url'), 16 | _ = require('underscore'); // use each method..... 17 | 18 | var server = { 19 | 20 | /** 21 | * request proxy route match status code 22 | */ 23 | status: { 24 | 25 | STATIC: 0, 26 | FORWARD: 1, 27 | UNMATCHED: 2 28 | }, 29 | /** 30 | * id for each proxy server 31 | */ 32 | serverId: 1, 33 | /** 34 | * mapping root 35 | */ 36 | rules: {}, 37 | /** 38 | * save proxy host 39 | */ 40 | proxies: { 41 | /** 42 | * from: Object {hostname, name} 43 | * rules: Object {static, forward, rewrite} 44 | */ 45 | }, 46 | /** 47 | * defualt port when config port is not pass in 48 | */ 49 | defaultPort: 80, 50 | /** 51 | * defualt port for https 52 | */ 53 | defualtHttpsPort: 443, 54 | /** 55 | * module regexps 56 | */ 57 | regexps: { 58 | // validate host string 59 | HOST: /^[0-9a-zA-Z\.]+\:?[0-9]*$/, 60 | ROUTE_FORWARD: /^([A-Z]+:)?\/.*$/, 61 | // static request must be GET method 62 | ROUTE_STATIC: /^!([A-Z]+:)?\/.*$/ 63 | // ROUTE_REWRITE: /^\^.*/ 64 | }, 65 | /** 66 | * for creating request route matching rule 67 | */ 68 | str: { 69 | ROUTE_METHOD_PREFIX: '([A-Z]+:)?' 70 | }, 71 | /** 72 | * Proxy enter api 73 | */ 74 | proxy: function (hosts) { 75 | 76 | // handle each proxy config 77 | _.each(hosts, function (hostConfig) { 78 | 79 | var hostObj = { 80 | from: this.parseHost(hostConfig.from), 81 | routes: this.parseRouteRule(hostConfig) 82 | } 83 | 84 | var serverId = this.create(hostObj.from, hostObj.routes, hostConfig); 85 | this.saveHost(hostObj.from, hostObj.routes, serverId); 86 | 87 | }.bind(this)); 88 | }, 89 | /** 90 | * support as express connect middleware 91 | */ 92 | connect: function (config) { 93 | 94 | var proxy = new httpProxy.RoutingProxy(), 95 | connectConfig = { 96 | rules: this.parseRouteRule(config) 97 | }, 98 | _this = this; 99 | 100 | return function (req, res, next) { 101 | 102 | _this.proxyMiddleware(req, res, proxy, connectConfig, function (proxyStatus) { 103 | // processing in express 104 | if (proxyStatus.status !== _this.status.FORWARD) { 105 | next(); 106 | } 107 | // forward request 108 | else { 109 | console.log(proxyStatus.message); 110 | } 111 | }); 112 | } 113 | }, 114 | /** 115 | * create a server for the one proxy config 116 | */ 117 | create: function (from, routes, options) { 118 | 119 | var serverId = this.serverId, // for serverid hoisting 120 | // proxy server options 121 | proxyOptions = { 122 | // use client origin 123 | changeOrigin: true, 124 | target: { 125 | // https: true, 126 | // rejectUnauthorized: false 127 | } 128 | }, 129 | _this = this; // for hoisting context of `this` 130 | // nessary for fixing node-http-proxy rejectUnauthorized option not work bug 131 | process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; 132 | // create proxy server 133 | var server = httpProxy.createServer( 134 | /** 135 | * middleware 136 | * Cross-Domain-Access 137 | */ 138 | function (req, res, next) { 139 | // TODO 140 | next(); 141 | }, 142 | function (req, res, proxy) { 143 | 144 | _this.proxyMiddleware(req, res, proxy, _this.proxies[serverId], function (statusObj) { 145 | console.log(statusObj.message); 146 | }); 147 | 148 | /** 149 | * must listen hostname, otherwise it will be fail when repeat listening 150 | * localhost in the some port 151 | */ 152 | }).listen(from.port, from.hostname) 153 | .on('error', function (e) { 154 | // catch server listen error 155 | console.log('Create proxy error,'.red.grey + "cause by can't listen host from " + 156 | from.hostname.yellow.grey + ' : ' + from.port.toString().blue.grey); 157 | }); 158 | 159 | console.log('Listen from ' + from.hostname.green.grey + ' : ' + from.port.toString().blue.grey); 160 | 161 | // Custom error 162 | server.proxy.on('proxyError', function (err, req, res) { 163 | res.writeHead(500, { 164 | 'Content-Type': 'text/plain' 165 | }); 166 | res.end('Something went wrong. And we are reporting a custom error message.\n' + err); 167 | }); 168 | 169 | return this.serverId ++; 170 | }, 171 | /** 172 | * Proxy middle for handling request and response 173 | */ 174 | proxyMiddleware: function (req, res, proxy, config, next) { 175 | 176 | var from = this.parseHost(req.headers.host), 177 | method = req.method, 178 | // rewrite url to originUrl for proxy agent 179 | requestURL = req.url = req.url || req.originalUrl, 180 | url = method.toUpperCase() + ':' + requestURL, 181 | forwardRouteObj = null; 182 | 183 | // get proxy config by proxy server id 184 | var proxyConfig = config; 185 | 186 | if (this.staticMatched(url, proxyConfig.rules.static)) { 187 | // match route is static file response 188 | var directory = path.resolve(process.cwd(), '.'); 189 | 190 | // send static files without server 191 | staticServer.sendfile(req, res, directory); 192 | 193 | next({ 194 | status: this.status.STATIC, 195 | message: method.blue.grey + ' ' + requestURL + ' from '.green.grey + from.hostname + ':' + from.port.toString().blue.grey 196 | }); 197 | 198 | } else if (forwardRouteObj = this.forwardMatched(url, proxyConfig.rules.forward)) { 199 | // set headers of config 200 | this.setHeaders(req, res, forwardRouteObj.options.headers); 201 | 202 | var forwardObj = forwardRouteObj.forward, 203 | // forward options 204 | proxyForwardOptions = { 205 | changeOrigin: true, 206 | host: forwardObj.hostname, 207 | port: forwardObj.port 208 | }; 209 | 210 | if (forwardRouteObj.options.https) { 211 | // set https forward options 212 | proxyForwardOptions = _.extend(proxyForwardOptions, { 213 | target: { 214 | https: true, 215 | rejectUnauthorized: false 216 | } 217 | }); 218 | } 219 | 220 | // forward to remote server 221 | proxy.proxyRequest(req, res, proxyForwardOptions); 222 | 223 | next({ 224 | status: this.status.FORWARD, 225 | message: 'forward '.yellow.grey + method.blue.grey + ' ' + requestURL + ' from '.green.grey + from.hostname + ':' + 226 | from.port.toString().blue.grey +' to '.green.grey + forwardObj.protocal + '://' + forwardObj.hostname + ':' + 227 | forwardObj.port.toString().blue.grey + requestURL 228 | }); 229 | } 230 | else { 231 | next({ 232 | status: this.status.UNMATCHED, 233 | message: "Sorry proxy error, http-route-proxy can't match any forward rule, please check your proxy config".red 234 | }); 235 | } 236 | }, 237 | /** 238 | * Set headers by proxy config 239 | */ 240 | setHeaders: function (req, res, headers) { 241 | var reqHeaders = headers && headers.req ? headers.req: {}, 242 | resHeaders = headers && headers.res ? headers.res: {}, 243 | origin = req.headers.origin || req.headers.host; 244 | 245 | // if exist Origin, use "Cross-Domain-Access" 246 | if (origin) { 247 | // currently, we only support receiving http request 248 | res.setHeader('access-control-allow-origin', 'http://' + origin); 249 | res.setHeader('access-control-allow-credentials', true); 250 | } 251 | //rewrite header on config 252 | if (headers) { 253 | _.each(reqHeaders, function (value, key) { 254 | req.headers[key] = value; 255 | }); 256 | _.each(resHeaders, function (value, key) { 257 | res.setHeader(key, value); 258 | }); 259 | } 260 | // avoid node-http-proxy to rewrite headers 261 | var setHeader = res.setHeader; 262 | res.setHeader = function (key, value) { 263 | if (!resHeaders[key]) setHeader.call(res, key, value); 264 | } 265 | 266 | }, 267 | /** 268 | * host is a string, should be parse to hostname + port object format 269 | */ 270 | parseHost: function (host, options) { 271 | options = options || {}; 272 | 273 | var hostChunks = host.match(/^([a-z]+)\:\/\/(.*)$/), 274 | protocal = hostChunks? hostChunks[1]: '', 275 | hostSection = hostChunks? hostChunks[2]: host, 276 | hostname = hostSection.split(':')[0], 277 | port = hostSection.split(':')[1], 278 | protocalObj = this.protocal(protocal), 279 | protocalOptions = protocalObj.unknow ? options: protocalObj; 280 | 281 | return { 282 | protocal: this.options2protocal(protocalOptions), 283 | protocalOption: protocalOptions, 284 | hostname: hostname, 285 | port: port ? parseInt(port) : (protocalOptions.https ? this.defualtHttpsPort : this.defaultPort) 286 | }; 287 | }, 288 | /** 289 | * save each host config 290 | */ 291 | saveHost: function (fromObj, routeRules, serverId) { 292 | 293 | // save proxy config 294 | this.proxies[serverId] = { 295 | 296 | from: fromObj, 297 | rules: routeRules 298 | 299 | }; 300 | 301 | }, 302 | /** 303 | * Check route if has default action , if has not, put it on first 304 | */ 305 | checkDefaultAction: function (routes) { 306 | 307 | var isContainDefaultAction = false; 308 | 309 | _.each(routes, function (item) { 310 | if (item == '/' || item.action == '/') { 311 | isContainDefaultAction = true; 312 | return true; 313 | } 314 | }); 315 | // put default action on the first tuples 316 | if (!isContainDefaultAction) routes.unshift('/'); 317 | return routes; 318 | }, 319 | /** 320 | * validate host string 321 | */ 322 | validHost: function (host) { 323 | return this.regexps.HOST.exec(host) ? true : false; 324 | }, 325 | /** 326 | * 327 | * @return Object 328 | */ 329 | protocal: function (protocal) { 330 | var protocalObj = { 331 | http: false, 332 | https: false, 333 | websocket: false, 334 | unknow: false 335 | }; 336 | if (protocal === 'http') { 337 | protocalObj.http = true; 338 | } else if (protocal === 'https') { 339 | protocalObj.https = true; 340 | } else if (protocal === 'ws') { 341 | protocalObj.websocket = true; 342 | } else { 343 | protocalObj.unknow = true; 344 | } 345 | return protocalObj; 346 | }, 347 | /** 348 | * protocal option to protocal name 349 | */ 350 | options2protocal: function (options) { 351 | 352 | if (options.https) { 353 | return 'https'; 354 | } else if (options.websocket) { 355 | return 'ws'; 356 | } else if (options.http) { 357 | return 'http'; 358 | } else { 359 | return 'http'; 360 | } 361 | }, 362 | /** 363 | * parse each route rule to url matched rule 364 | */ 365 | parseRouteRule: function (options) { 366 | options = util.copy(options); 367 | 368 | var routes = options.route || []; 369 | // if route config is empty, give it default 370 | routes = this.checkDefaultAction(routes); 371 | 372 | var _this = this, 373 | forwards = [], // forward route rules 374 | // rewrites = [], // currently it's useless 375 | statices = [], // static route rules 376 | forwardObj = null; 377 | 378 | _.each(routes, function (route) { 379 | 380 | // route detail config 381 | if (_.isObject(route)) { 382 | 383 | var routeOptions = route, 384 | forwardObj = _this.parseHost(routeOptions.forward); 385 | 386 | routeOptions = _.extend(routeOptions, forwardObj.protocalOption); 387 | 388 | forwards.push({ 389 | rule: _this.forwardRouteRule2Regexp(routeOptions.action), 390 | forward: forwardObj, 391 | options: routeOptions 392 | }); 393 | 394 | } 395 | // forward route match, route rule in shorthand 396 | else if (_this.regexps.ROUTE_FORWARD.exec(route)) { 397 | // parse forward host 398 | var forwardObj = _this.parseHost(options.to, options); 399 | // set foward options 400 | options = _.extend(options, forwardObj.protocalOption); 401 | // save config 402 | forwards.push({ 403 | rule: _this.forwardRouteRule2Regexp(route), 404 | forward: forwardObj, 405 | options: options 406 | }); 407 | 408 | } 409 | // static route match, route rule in shorthand 410 | else if (_this.regexps.ROUTE_STATIC.exec(route)) { 411 | statices.push(_this.staticRouteRule2Regexp(route)); 412 | } 413 | }); 414 | 415 | return { 416 | forward: forwards, 417 | // rewrite: rewrites, 418 | static: statices 419 | }; 420 | }, 421 | /** 422 | * url route rule to regexp 423 | */ 424 | string2Regexp: function (rule) { 425 | rule = rule.replace('/','\\/'); 426 | rule = '^' + rule + '.*$'; 427 | return new RegExp(rule); 428 | }, 429 | /** 430 | * find the rule whether is static route rule and return regexpess rule. 431 | */ 432 | staticRouteRule2Regexp: function (rule) { 433 | var matches = this.regexps.ROUTE_STATIC.exec(rule); 434 | 435 | rule = rule.replace(/^!/, ''); 436 | 437 | if (!matches[1]) { 438 | rule = this.str.ROUTE_METHOD_PREFIX + rule; 439 | } 440 | return this.string2Regexp(rule); 441 | }, 442 | /** 443 | * find the rule whether is forward route rule and return regexpess rule. 444 | */ 445 | forwardRouteRule2Regexp: function (rule) { 446 | var matches = this.regexps.ROUTE_FORWARD.exec(rule); 447 | 448 | if (!matches[1]) { 449 | rule = this.str.ROUTE_METHOD_PREFIX + '(@)'.replace('@', rule); 450 | } 451 | return this.string2Regexp(rule); 452 | }, 453 | /** 454 | * Match url by forward route rule 455 | * @return {Object} routeRule 456 | */ 457 | forwardMatched: function (url, forwardRules) { 458 | var matchedRoute = null; 459 | 460 | forwardRules.some(function (ruleObj) { 461 | if (ruleObj.rule.exec(url)) { 462 | matchedRoute = ruleObj; 463 | return true; 464 | } 465 | }); 466 | 467 | return matchedRoute; 468 | }, 469 | /** 470 | * Match url by static route rule 471 | * @return isMatched 472 | */ 473 | staticMatched: function (url, staticRules) { 474 | var isMatched = false; 475 | 476 | _.each(staticRules, function (rule) { 477 | if (rule.exec(url)) { 478 | isMatched = true; 479 | return true; 480 | } 481 | }); 482 | 483 | return isMatched; 484 | } 485 | } 486 | 487 | module.exports = server; -------------------------------------------------------------------------------- /lib/static.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | send = require('send'), 3 | url = require('url'), 4 | colors = require('colors'), 5 | path = require('path'); 6 | 7 | var staticServer = { 8 | 9 | // server id with increasing 10 | serverId: 0, 11 | // serverr config with serverId as key 12 | server: {}, 13 | 14 | init: function (hostname, port, options) { 15 | hostname = hostname || 'localhost'; 16 | port = port || 9000; 17 | options = options || {}; 18 | 19 | var directory = options.directory || '.', 20 | serverId = this.serverId ++; 21 | 22 | this.server[serverId] = { 23 | hostname: hostname, 24 | port: port, 25 | directory: directory 26 | }; 27 | 28 | var _this = this; 29 | http.createServer(function(req, res) { 30 | // resolve root directory to local path 31 | directory = path.resolve(process.cwd(), directory); 32 | // response file sending 33 | _this.sendfile(req, res, directory); 34 | 35 | }).listen(port, hostname); 36 | // marked server instance id 37 | return serverId; 38 | }, 39 | /** 40 | * send static file or directory which is requested 41 | */ 42 | sendfile: function (req, res, directory) { 43 | // error handler 44 | function error(err) { 45 | res.statusCode = err.status || 500; 46 | err.message && console.log(err.message.red); 47 | res.end(err.message); 48 | } 49 | // directory redirect handler 50 | function redirect() { 51 | res.statusCode = 301; 52 | res.setHeader('Location', req.url + '/'); 53 | res.end('Redirecting to ' + req.url + '/'); 54 | } 55 | // send 56 | return send(req, url.parse(req.url).pathname) 57 | .root(directory) 58 | .on('error', error) 59 | .on('directory', redirect) 60 | .pipe(res); 61 | } 62 | } 63 | 64 | module.exports = staticServer; 65 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var util = { 2 | copy: function (obj) { 3 | return JSON.parse(JSON.stringify(obj)); 4 | } 5 | } 6 | 7 | module.exports = util; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-route-proxy", 3 | "version": "0.2.2", 4 | "description": "Convenient HTTP proxy for cross-domail request, request forward.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test/", 8 | "start": "node index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/switer/http-proxy.git" 13 | }, 14 | "keywords": [ 15 | "http", 16 | "https", 17 | "proxy", 18 | "cross-domain", 19 | "request", 20 | "forward", 21 | "express", 22 | "connect" 23 | ], 24 | "author": "switer", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/switer/http-proxy/issues" 28 | }, 29 | "dependencies": { 30 | "colors": "~0.6.2", 31 | "http-proxy": "~0.10.3", 32 | "underscore": "~1.5.2", 33 | "send": "~0.1.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var proxyServer = require('../index'); 2 | 3 | /** 4 | * proxy configs 5 | */ 6 | proxyServer.proxy([ 7 | // { 8 | // from: 'localhost:9004', 9 | // to: 'https://www.google.com' 10 | // }, 11 | { 12 | from: 'localhost:9004', 13 | to: 'https://kyfw.12306.cn' 14 | }, 15 | { 16 | from: 'localhost:9003', 17 | route: [{ 18 | action: '/', 19 | forward: 'https://www.google.com' 20 | }, 21 | '!/public', // forward to local static files 22 | { 23 | action: '/otn', 24 | forward: 'localhost:9004', 25 | headers: { 26 | req: { 27 | origin: 'https://kyfw.12306.cn/', 28 | referer: 'https://kyfw.12306.cn/' 29 | } 30 | } 31 | }, { 32 | action: '/otn/login/init', 33 | forward: 'kyfw.12306.cn', 34 | headers: { 35 | req: { 36 | Origin: 'kyfw.12306.cn' 37 | }, 38 | res: { 39 | Doo: 'guankaishe' 40 | } 41 | } 42 | } 43 | ] 44 | }, 45 | // common config 46 | { 47 | // origin host + port 48 | from: 'localhost:9002', 49 | // forward host + port 50 | to: 'www.google.com', 51 | // protocal option 52 | https: true, 53 | // reset headers 54 | headers: { 55 | req: { 56 | origin: 'https://www.google.com', 57 | referer: 'https://www.google.com' 58 | }, 59 | res: { 60 | Doo: 'www.google.com' 61 | } 62 | } 63 | }, 64 | // host with 80 port 65 | { 66 | from: 'localhost:9001', 67 | to: 'github.com', 68 | https: true, 69 | headers: { 70 | req: { 71 | origin: 'https://github.com', 72 | referer: 'https://github.com' 73 | } 74 | } 75 | } 76 | ]); 77 | -------------------------------------------------------------------------------- /test/static.js: -------------------------------------------------------------------------------- 1 | var staticServer = require('../static'); 2 | staticServer.init(); --------------------------------------------------------------------------------