diff --git a/README.md b/README.md index 8f07acf..3fb7bcf 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](https://github.com/feross/standard) # local-web-server -Fires up a simple, CORS-enabled, static web server on a given port. Use for local web development or file sharing (directory browsing enabled). **Requires node v4.0.0 or higher**. +A static web-server for productive front-end development. -![local-web-server](http://75lb.github.io/local-web-server/ws.gif) +**Requires node v4.0.0 or higher**. ## Install Ensure [node.js](http://nodejs.org) is installed first. Linux/Mac users may need to run the following commands with `sudo`. diff --git a/bin/ws.js b/bin/cli.js similarity index 83% rename from bin/ws.js rename to bin/cli.js index d08fb0a..dc5b410 100755 --- a/bin/ws.js +++ b/bin/cli.js @@ -19,13 +19,15 @@ try { halt(err.message) } -options.stored = Object.assign({ - blacklist: [] -}, loadConfig('local-web-server')) +options.stored = loadConfig('local-web-server') + options.builtIn = { port: 8000, - directory: process.cwd() + root: process.cwd(), // root dir when using multiple static dirs + directory: process.cwd(), + proxyRoutes: [], + blacklist: [] } /* override built-in defaults with stored config and then command line args */ @@ -39,8 +41,9 @@ localWebServer({ serveIndex: { path: options.cli.server.directory, options: { icons: true } }, log: { format: options.cli.server['log-format'] }, compress: options.cli.server.compress, - mime: options.stored.mime, - blacklist: options.stored.blacklist.map(regexp => RegExp(regexp, "i")) + mime: options.cli.server.mime, + blacklist: options.cli.server.blacklist.map(regexp => RegExp(regexp, "i")), + proxyRoutes: options.cli.server.proxyRoutes }).listen(options.cli.server.port, onServerUp) function halt (message) { diff --git a/lib/local-web-server.js b/lib/local-web-server.js index 5d508f1..ca081fc 100644 --- a/lib/local-web-server.js +++ b/lib/local-web-server.js @@ -10,6 +10,10 @@ const streamLogStats = require('stream-log-stats') const cors = require('kcors') const conditional = require('koa-conditional-get'); const etag = require('koa-etag'); +const _ = require('koa-route') +const mount = require('koa-mount') +const httpProxy = require('http-proxy') +const pathToRegexp = require('path-to-regexp') /** * @module local-web-server @@ -22,16 +26,88 @@ function getApp (options) { serveIndex: {}, log: {}, compress: false, - blacklist: [] + blacklist: [], + directories: [], + proxyRoutes: [] }, options) const log = options.log log.options = log.options || {} const app = new Koa() + const _use = app.use + app.use = x => _use.call(app, convert(x)) + + const proxy = httpProxy.createProxyServer({ + changeOrigin: true + }) + + // app.use(_.all('/api/*', function * (apiPath) { + // this.response = false + // proxy.once('proxyReq', function (proxyReq, req, res, options) { + // proxyReq.path = `http://registry.npmjs.org/${apiPath}`; + // }) + // proxy.web(this.req, this.res, { target: `http://registry.npmjs.org/${apiPath}` }) + // })) + // app.use(mount('/gh', function * (next) { + // this.response = false + // proxy.web(this.req, this.res, { target: 'https://api.github.com' }) + // })) + // app.use(_.get('/:one/gh/:two', function * (one, two) { + // this.response = false + // proxy.once('proxyReq', function (proxyReq, req, res, options) { + // proxyReq.path = `https://api.github.com/${one}/${two}`; + // }) + // proxy.web(this.req, this.res, { target: `https://api.github.com/${one}/${two}` }) + // })) + // app.use(_.get('/*/yeah/:one/*', function * (one, two) { + // console.log(arguments); + // this.response = false + // proxy.once('proxyReq', function (proxyReq, req, res, options) { + // proxyReq.path = `https://api.github.com/${one}/${two}`; + // }) + // proxy.web(this.req, this.res, { target: `https://api.github.com/${one}/${two}` }) + // })) + + // const proxyRoutes = [ + // // { mount: '/api', to: 'http://registry.npmjs.org' }, + // // { mount: '/gh', to: 'http://https://api.github.com' }, + // { from: '/:one/gh/:two', to: 'https://api.github.com/${one}/${two}' }, + // { from: '/api/*', to: 'http://registry.npmjs.org/${0}' }, + // ] + + options.proxyRoutes.forEach(route => { + app.use(_.all(route.from, function * () { + const keys = [] + route.re = pathToRegexp(route.from, keys) + route.new = route.to + + this.response = false + keys.forEach((key, index) => { + const re = { + token: RegExp('\\$\\{' + key.name + '\\}', 'g'), + index: RegExp('\\$\\{' + index + '\\}', 'g') + } + route.new = route.new + .replace(re.token, arguments[index] || '') + .replace(re.index, arguments[index] || '') + + // console.log('=========='); + // console.log(arguments); + // console.log(re); + // console.log(index); + // console.log(route); + + }) + proxy.once('proxyReq', function (proxyReq) { + proxyReq.path = route.new; + }) + proxy.web(this.req, this.res, { target: route.new }) + })) + }) /* CORS: allow from any origin */ - app.use(convert(cors())) + app.use(cors()) /* path blacklist */ if (options.blacklist.length) { @@ -44,8 +120,8 @@ function getApp (options) { }) } - app.use(convert(conditional())) - app.use(convert(etag())) + app.use(conditional()) + app.use(etag()) /* mime-type overrides */ if (options.mime) { @@ -62,7 +138,7 @@ function getApp (options) { /* compress response */ if (options.compress) { - app.use(convert(compress())) + app.use(compress()) } /* special case log formats */ @@ -78,16 +154,24 @@ function getApp (options) { log.format = 'common' log.options.stream = streamLogStats({ refreshRate: 500 }) } - if (log.format) app.use(convert(morgan.middleware(log.format, log.options))) + if (log.format) app.use(morgan.middleware(log.format, log.options)) + // options.static.root = [ + // { route: '/one', root: 'lib' }, + // { route: '/two', root: 'node_modules' } + // ] /* serve static files */ if (options.static.root) { - app.use(convert(serve(options.static.root, options.static.options))) + app.use(serve(options.static.root, options.static.options)) + // options.static.root.forEach(config => { + // app.use(mount(config.route, serve(config.root))) + // app.use(mount(config.route, serveIndex(config.root))) + // }) } /* serve directory index */ if (options.serveIndex.path) { - app.use(convert(serveIndex(options.serveIndex.path, options.serveIndex.options))) + app.use(serveIndex(options.serveIndex.path, options.serveIndex.options)) } return app diff --git a/package.json b/package.json index be4ceee..94ba584 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.5.23", "description": "Lightweight static web server, zero configuration. Perfect for front-end devs.", "bin": { - "ws": "./bin/ws.js" + "ws": "./bin/cli.js" }, "main": "lib/local-web-server.js", "license": "MIT", @@ -29,6 +29,7 @@ "dependencies": { "command-line-args": "^2.0.2", "config-master": "^2", + "http-proxy": "^1.12.0", "kcors": "^1.0.1", "koa": "^2.0.0-alpha.3", "koa-charset": "^1.1.4", @@ -37,11 +38,13 @@ "koa-convert": "^1.1.0", "koa-etag": "^2.1.0", "koa-morgan": "^0.4.0", + "koa-mount": "^1.3.0", "koa-rewrite": "^1.1.1", "koa-route": "^2.4.2", "koa-serve-index": "^1.1.0", "koa-static": "^1.5.2", "morgan": "^1.0.0", + "path-to-regexp": "^1.2.1", "req-then": "^0.2.2", "stream-log-stats": "^1" }, diff --git a/test/fixture/one/file.txt b/test/fixture/one/file.txt new file mode 100644 index 0000000..5626abf --- /dev/null +++ b/test/fixture/one/file.txt @@ -0,0 +1 @@ +one diff --git a/test/test.js b/test/test.js index 8a998ba..46e954a 100644 --- a/test/test.js +++ b/test/test.js @@ -119,3 +119,27 @@ test('blacklist', function (t) { }) }) }) + +test.skip('directories: should serve index and static files', function(t){ + t.plan(1) + const app = localWebServer({ + log: { format: 'none' }, + directories: [ + __dirname + '/fixture/one' + ] + }) + launchServer(app, { path: '/something.php', onSuccess: response => { + t.ok(/text\/plain/.test(response.res.headers['content-type'])) + }}) +}) + +test('proxy', function(t){ + t.plan(1) + const app = localWebServer({ + log: { format: 'none' }, + proxy: [] + }) + launchServer(app, { path: '/something.php', onSuccess: response => { + t.ok(/text\/plain/.test(response.res.headers['content-type'])) + }}) +})