diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..d007aa8 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1 @@ +repo_token: w9HmlMl9558e1LpP9p62YgYutkVE9PqtN diff --git a/.gitignore b/.gitignore index 3c3629e..6cb772b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +tmp diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 0a54ccb..0000000 --- a/.jshintrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "bitwise": true, - "camelcase": true, - "eqeqeq": true, - "globals": { "describe" : false, "it": false, "beforeEach": false }, - "globalstrict": false, - "indent": 4, - "latedef": true, - "laxbreak": true, - "maxparams": 3, - "multistr": true, - "newcap": true, - "node": true, - "quotmark": "double", - "trailing": true, - "undef": true, - "unused": true -} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..faf9484 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: +- '5.0' +- '4.2' diff --git a/README.md b/README.md index 0ba3ce2..77fc8fa 100644 --- a/README.md +++ b/README.md @@ -1,193 +1,231 @@ [![view on npm](http://img.shields.io/npm/v/local-web-server.svg)](https://www.npmjs.org/package/local-web-server) -[![npm module downloads per month](http://img.shields.io/npm/dm/local-web-server.svg)](https://www.npmjs.org/package/local-web-server) +[![npm module downloads](http://img.shields.io/npm/dt/local-web-server.svg)](https://www.npmjs.org/package/local-web-server) +[![Build Status](https://travis-ci.org/75lb/local-web-server.svg?branch=master)](https://travis-ci.org/75lb/local-web-server) [![Dependency Status](https://david-dm.org/75lb/local-web-server.svg)](https://david-dm.org/75lb/local-web-server) [![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). +A simple 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`. +## Synopsis +For the examples below, we assume we're in a project directory looking like this: -### Globally ```sh -$ npm install -g local-web-server +. +├── css +│   └── style.css +├── index.html +└── package.json ``` -### Bundled with your project -```sh -$ npm install local-web-server --save-dev -``` +All paths/routes are specified using [express syntax](http://expressjs.com/guide/routing.html#route-paths). -Then add an `start` script to your `package.json` (the standard npm approach): -```json -{ - "name": "my-web-app", - "version": "1.0.0", - "scripts": { - "start": "ws" - } -} -``` -This simplifies a rather specific-looking instruction set like: +### Static site +Fire up your static site on the default port: ```sh -$ npm install -$ npm install -g local-web-server $ ws +serving at http://localhost:8000 ``` -to the following, server implementation and launch details abstracted away: +### Single Page Application + +You're building a web app with client-side routing, so mark `index.html` as the SPA. ```sh -$ npm install -$ npm start +$ ws --spa index.html ``` -## Usage -``` -Usage -$ ws -$ ws --config -$ ws --help +By default, typical SPA urls (e.g. `/user/1`, `/login`) would return `404 Not Found` as a file does not exist with that path. By marking `index.html` as the SPA you create this rule: -Server --p, --port Web server port --f, --log-format If a format is supplied an access log is written to stdout. If not, a statistics view is displayed. Use a - preset ('none', 'dev','combined', 'short', 'tiny' or 'logstalgia') or supply a custom format (e.g. ':method -> - :url'). --d, --directory Root directory, defaults to the current directory --c, --compress Enable gzip compression, reduces bandwidth. --r, --refresh-rate Statistics view refresh rate in ms. Defaults to 500. +*If a static file at the requested path exists (e.g. `/css/style.css`) then serve it, if it does not (e.g. `/login`) then serve the specified SPA and handle the route client-side.* -Misc --h, --help Print these usage instructions ---config Print the stored config -``` +### Access Control -From the folder you wish to serve, run: +By default, access to all files is allowed (including dot files). Use `--forbid` to establish a blacklist: ```sh -$ ws +$ ws --forbid '*.json' '*.yml' serving at http://localhost:8000 ``` -If you wish to serve a different directory, run: -```sh -$ ws -d ~/mysite/ -serving /Users/Lloyd/mysite at http://localhost:8000 -``` +### URL rewriting + +Your application requested `/css/style.css` but it's stored at `/build/css/style.css`. To avoid a 404 you need a rewrite rule: -If you wish to override the default port (8000), use `--port` or `-p`: ```sh -$ ws --port 9000 -serving at http://localhost:9000 +$ ws --rewrite '/css/style.css -> /build/css/style.css' ``` -To add compression, reducing bandwidth, increasing page load time (by 10-15% on my Macbook Air) +Or, more generally (matching any stylesheet under `/css`): + ```sh -$ ws --compress +$ ws --rewrite '/css/:stylesheet -> /build/css/:stylesheet' ``` -### Logging -Passing a value to `--log-format` will write an access log to `stdout`. +With a deep CSS directory structure it may be easier to mount the entire contents of `/build/css` to the `/css` path: -Either use a built-in [morgan](https://github.com/expressjs/morgan) logger preset: ```sh -$ ws --log-format short +$ ws --rewrite '/css/* -> /build/css/$1' ``` -Or a custom [morgan](https://github.com/expressjs/morgan) log format: +this rewrites `/css/a` as `/build/css/a`, `/css/a/b/c` as `/build/css/a/b/c` etc. + + +#### Proxied requests + +If the `to` URL contains a remote host, local-web-server will act as a proxy - fetching and responding with the remote resource. + +Mount the npm registry locally: ```sh -$ ws -f ':method -> :url' +$ ws --rewrite '/npm/* -> http://registry.npmjs.org/$1' ``` -Or silence: +Map local requests for repo data to the Github API: ```sh -$ ws -f none +$ ws --rewrite '/:user/repos/:name -> https://api.github.com/repos/:user/:name' ``` -## Storing default options -To store per-project options, saving you the hassle of inputting them everytime, store them in the `local-web-server` property of your project's `package.json`: +### Stored config + +Use the same port and blacklist every time? Persist it to `package.json`: ```json { - "name": "my-project", - "version": "0.11.8", - "local-web-server":{ - "port": 8100 + "name": "example", + "version": "1.0.0", + "local-web-server": { + "port": 8100, + "forbid": "*.json" } } ``` -Or in a `.local-web-server.json` file stored in the directory you want to serve (typically the root folder of your site): +or `.local-web-server.json` ```json { "port": 8100, - "log-format": "tiny" + "forbid": "*.json" } ``` -Or store global defaults in a `.local-web-server.json` file in your home directory. -```json -{ - "port": 3000, - "refresh-rate": 1000 -} +local-web-server will merge and use all config found, searching from the current directory upward. In the case both `package.json` and `.local-web-server.json` config is found in the same directory, `.local-web-server.json` will take precedence. Command-line options take precedence over all. + +To inspect stored config, run: +```sh +$ ws --config ``` -All stored defaults are overriden by options supplied at the command line. +### Logging +By default, local-web-server outputs a simple, dynamic statistics view. To see traditional web server logs, use `--log-format`: -To view your stored defaults, run: +```sh +$ ws --log-format combined +serving at http://localhost:8000 +::1 - - [16/Nov/2015:11:16:52 +0000] "GET / HTTP/1.1" 200 12290 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2562.0 Safari/537.36" +``` +The format value supplied is passed directly to [morgan](https://github.com/expressjs/morgan). The exception is `--log-format none` which disables all output. + +### Other usage + +#### Compression + +Serve gzip-compressed resources, where applicable ```sh -$ ws --config +$ ws --compress ``` -## mime-types -You can set additional mime-type/extension mappings, or override the defaults by setting a `mime` value in your local config. This value is passed directly to [mime.define()](https://github.com/broofa/node-mime#mimedefine). Example: +#### Disable caching + +Disable etag response headers, forcing resources to be served in full every time. +```sh +$ ws --no-cache +``` + +#### mime-types +You can set additional mime-type/extension mappings, or override the defaults by setting a `mime` value in the stored config. This value is passed directly to [mime.define()](https://github.com/broofa/node-mime#mimedefine). Example: ```json { - "mime": { - "text/plain": [ "php", "pl" ] - } + "mime": { + "text/plain": [ "php", "pl" ] + } } ``` -## Use with Logstalgia -local-web-server is compatible with [logstalgia](http://code.google.com/p/logstalgia/). +#### Log Visualisation +Instructions for how to visualise log output using goaccess, logstalgia or gltail [here](https://github.com/75lb/local-web-server/blob/master/doc/visualisation.md). + +## Install +Ensure [node.js](http://nodejs.org) is installed first. Linux/Mac users may need to run the following commands with `sudo`. -### Install Logstalgia -On MacOSX, install with [homebrew](http://brew.sh): ```sh -$ brew install logstalgia +$ npm install -g local-web-server ``` -Alternatively, [download a release for your system from github](https://github.com/acaudwell/Logstalgia/releases/latest). +This will install the `ws` tool globally. To see the available options, run: +```sh +$ ws --help +``` + +## Distribute with your project +The standard convention with client-server applications is to add an `npm start` command to launch the server component. + +1\. Install the server as a dev dependency -Then pipe the `logstalgia` output format directly into logstalgia for real-time visualisation: ```sh -$ ws -f logstalgia | logstalgia - +$ npm install local-web-server --save-dev +``` + +2\. Add a `start` command to your `package.json`: + +```json +{ + "name": "example", + "version": "1.0.0", + "local-web-server": { + "port": 8100, + "forbid": "\\.json$" + }, + "scripts": { + "start": "ws" + } +} ``` -![local-web-server with logstalgia](http://75lb.github.io/local-web-server/logstagia.gif) +3\. Document how to build and launch your site -## Use with glTail -To use with [glTail](http://www.fudgie.org), write your log to disk using the "default" format: ```sh -$ ws -f default > web.log +$ npm install +$ npm start +serving at http://localhost:8100 ``` -Then specify this file in your glTail config: +## API Reference + + +## local-web-server + +### localWebServer([options]) ⏏ +Returns a Koa application -```yaml -servers: - dev: - host: localhost - source: local - files: /Users/Lloyd/Documents/MySite/web.log - parser: apache - color: 0.2, 0.2, 1.0, 1.0 +**Kind**: Exported function +**Params** +- [options] object - options + - [.static] object - koajs/static config + - [.root] string - root directory + - [.options] string - options + - [.serveIndex] object - koa-serve-index config + - [.path] string - root directory + - [.options] string - options + - [.forbid] Array.<string> - a list of forbidden routes. + +**Example** +```js +const localWebServer = require('local-web-server') +localWebServer().listen(8000) ``` -© 2015 Lloyd Brookes <75pound@gmail.com> +* * * + +© 2015 Lloyd Brookes <75pound@gmail.com>. Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown). diff --git a/bin/cli.js b/bin/cli.js new file mode 100755 index 0000000..69e9e06 --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,99 @@ +#!/usr/bin/env node +'use strict' +const localWebServer = require('../') +const cliOptions = require('../lib/cli-options') +const commandLineArgs = require('command-line-args') +const ansi = require('ansi-escape-sequences') +const loadConfig = require('config-master') +const path = require('path') + +const cli = commandLineArgs(cliOptions.definitions) +const usage = cli.getUsage(cliOptions.usageData) +const stored = loadConfig('local-web-server') +const options = collectOptions() + +// TODO summary line on server launch + +if (options.misc.help) { + console.log(usage) + process.exit(0) +} +if (options.misc.config) { + console.log(JSON.stringify(options.server, null, ' ')) + process.exit(0) +} + +localWebServer({ + static: { + root: options.server.directory, + options: { + hidden: true + } + }, + serveIndex: { + path: options.server.directory, + options: { + icons: true, + hidden: true + } + }, + log: { + format: options.server['log-format'] + }, + compress: options.server.compress, + mime: options.server.mime, + forbid: options.server.forbid, + spa: options.server.spa, + 'no-cache': options.server['no-cache'], + rewrite: options.server.rewrite +}).listen(options.server.port, onServerUp) + +function halt (err) { + console.log(ansi.format(`Error: ${err.message}`, 'red')) + console.log(usage) + process.exit(1) +} + +function onServerUp () { + console.error(ansi.format( + path.resolve(options.server.directory) === process.cwd() + ? `serving at [underline]{http://localhost:${options.server.port}}` + : `serving [underline]{${options.server.directory}} at [underline]{http://localhost:${options.server.port}}` + )) +} + +function collectOptions () { + let options = {} + + /* parse command line args */ + try { + options = cli.parse() + } catch (err) { + halt(err) + } + + const builtIn = { + port: 8000, + directory: process.cwd(), + forbid: [], + rewrite: [] + } + + if (options.server.rewrite) { + options.server.rewrite = parseRewriteRules(options.server.rewrite) + } + + /* override built-in defaults with stored config and then command line args */ + options.server = Object.assign(builtIn, stored, options.server) + return options +} + +function parseRewriteRules (rules) { + return rules && rules.map(rule => { + const matches = rule.match(/(\S*)\s*->\s*(\S*)/) + return { + from: matches[1], + to: matches[2] + } + }) +} diff --git a/bin/ws.js b/bin/ws.js deleted file mode 100755 index d2bf914..0000000 --- a/bin/ws.js +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env node -'use strict' -var dope = require('console-dope') -var http = require('http') -var cliArgs = require('command-line-args') -var o = require('object-tools') -var t = require('typical') -var path = require('path') -var loadConfig = require('config-master') -var homePath = require('home-path') -var logStats = require('stream-log-stats') -var connect = require('connect') -var morgan = require('morgan') -var serveStatic = require('serve-static') -var directory = require('serve-index') -var compress = require('compression') -var cliOptions = require('../lib/cli-options') - -/* specify the command line arg definitions and usage forms */ -var cli = cliArgs(cliOptions) -var usage = cli.getUsage({ - title: 'local-web-server', - description: 'Lightweight static web server, zero configuration.', - footer: 'Project home: [underline]{https://github.com/75lb/local-web-server}', - usage: { - forms: [ - '$ ws ', - '$ ws --config', - '$ ws --help' - ] - }, - groups: { - server: 'Server', - misc: 'Misc' - } -}) - -/* parse command line args */ -try { - var wsOptions = cli.parse() -} catch (err) { - halt(err.message) -} - -/* Load and merge together options from -- ~/.local-web-server.json -- {cwd}/.local-web-server.json -- the `local-web-server` property of {cwd}/package.json -*/ -var storedConfig = loadConfig( - path.join(homePath(), '.local-web-server.json'), - path.join(process.cwd(), '.local-web-server.json'), - { jsonPath: path.join(process.cwd(), 'package.json'), configProperty: 'local-web-server' } -) - -var builtInDefaults = { - port: 8000, - directory: process.cwd(), - 'refresh-rate': 500, - mime: {} -} - -/* override built-in defaults with stored config and then command line args */ -wsOptions.server = o.extend(builtInDefaults, storedConfig, wsOptions.server) - -/* user input validation */ -if (!t.isNumber(wsOptions.server.port)) { - halt('please supply a numeric port value') -} - -if (wsOptions.misc.config) { - dope.log('Stored config: ') - dope.log(storedConfig) - process.exit(0) -} else if (wsOptions.misc.help) { - dope.log(usage) -} else { - process.on('SIGINT', function () { - dope.showCursor() - dope.log() - process.exit(0) - }) - - dope.hideCursor() - launchServer() - - /* write launch information to stderr (stdout is reserved for web log output) */ - if (path.resolve(wsOptions.server.directory) === process.cwd()) { - dope.error('serving at %underline{%s}', 'http://localhost:' + wsOptions.server.port) - } else { - dope.error('serving %underline{%s} at %underline{%s}', wsOptions.server.directory, 'http://localhost:' + wsOptions.server.port) - } -} - -function halt (message) { - dope.red.log('Error: %s', message) - dope.log(usage) - process.exit(1) -} - -function launchServer () { - var app = connect() - - /* enable cross-origin requests on all resources */ - app.use(function (req, res, next) { - res.setHeader('Access-Control-Allow-Origin', '*') - next() - }) - - if (wsOptions.server['log-format'] !== 'none') app.use(getLogger()) - - /* --compress enables compression */ - if (wsOptions.server.compress) app.use(compress()) - - /* set the mime-type overrides specified in the config */ - serveStatic.mime.define(wsOptions.server.mime) - - /* enable static file server, including directory browsing support */ - app.use(serveStatic(path.resolve(wsOptions.server.directory))) - .use(directory(path.resolve(wsOptions.server.directory), { icons: true })) - - /* launch server */ - http.createServer(app) - .on('error', function (err) { - if (err.code === 'EADDRINUSE') { - halt('port ' + wsOptions.server.port + ' is already is use') - } else { - halt(err.message) - } - }) - .listen(wsOptions.server.port) -} - -function getLogger () { - /* log using --log-format (if supplied) */ - var logFormat = wsOptions.server['log-format'] - if (logFormat) { - if (logFormat === 'logstalgia') { - /* customised logger :date token, purely to satisfy Logstalgia. */ - morgan.token('date', function () { - var d = new Date() - return (d.getDate() + '/' + d.getUTCMonth() + '/' + d.getFullYear() + ':' + d.toTimeString()) - .replace('GMT', '').replace(' (BST)', '') - }) - logFormat = 'combined' - } - - return morgan(logFormat) - - /* if no `--log-format` was specified, pipe the default format output - into `log-stats`, which prints statistics to the console */ - } else { - return morgan('common', { stream: logStats({ refreshRate: wsOptions.server['refresh-rate'] }) }) - } -} diff --git a/doc/img/logstagia.gif b/doc/img/logstagia.gif new file mode 100644 index 0000000..22415e0 Binary files /dev/null and b/doc/img/logstagia.gif differ diff --git a/doc/visualisation.md b/doc/visualisation.md new file mode 100644 index 0000000..76078ad --- /dev/null +++ b/doc/visualisation.md @@ -0,0 +1,37 @@ +## Goaccess + +## Logstalgia +local-web-server is compatible with [logstalgia](http://code.google.com/p/logstalgia/). + +### Install Logstalgia +On MacOSX, install with [homebrew](http://brew.sh): +```sh +$ brew install logstalgia +``` + +Alternatively, [download a release for your system from github](https://github.com/acaudwell/Logstalgia/releases/latest). + +Then pipe the `logstalgia` output format directly into logstalgia for real-time visualisation: +```sh +$ ws -f logstalgia | logstalgia - +``` + +![local-web-server with logstalgia](https://raw.githubusercontent.com/75lb/local-web-server/master/doc/img/logstagia.gif) + +## glTail +To use with [glTail](http://www.fudgie.org), write your log to disk using the "default" format: +```sh +$ ws -f default > web.log +``` + +Then specify this file in your glTail config: + +```yaml +servers: + dev: + host: localhost + source: local + files: /Users/Lloyd/Documents/MySite/web.log + parser: apache + color: 0.2, 0.2, 1.0, 1.0 +``` diff --git a/example/forbid/.local-web-server.json b/example/forbid/.local-web-server.json new file mode 100644 index 0000000..dd8124a --- /dev/null +++ b/example/forbid/.local-web-server.json @@ -0,0 +1,5 @@ +{ + "forbid": [ + "/admin/*", "*.php" + ] +} diff --git a/example/forbid/admin/blocked.html b/example/forbid/admin/blocked.html new file mode 100644 index 0000000..f51dbee --- /dev/null +++ b/example/forbid/admin/blocked.html @@ -0,0 +1 @@ +

Forbidden page

diff --git a/example/forbid/allowed.html b/example/forbid/allowed.html new file mode 100644 index 0000000..4d0bd56 --- /dev/null +++ b/example/forbid/allowed.html @@ -0,0 +1 @@ +

A permitted page

diff --git a/example/forbid/index.html b/example/forbid/index.html new file mode 100644 index 0000000..b2b30ff --- /dev/null +++ b/example/forbid/index.html @@ -0,0 +1,5 @@ +

Forbidden routes

+ +

+ Notice you can access this page, but not this admin page or php file. +

diff --git a/test/something.php b/example/forbid/something.php similarity index 100% rename from test/something.php rename to example/forbid/something.php diff --git a/example/mime-override/.local-web-server.json b/example/mime-override/.local-web-server.json new file mode 100644 index 0000000..d569d29 --- /dev/null +++ b/example/mime-override/.local-web-server.json @@ -0,0 +1,5 @@ +{ + "mime": { + "text/plain": [ "php" ] + } +} diff --git a/example/mime-override/something.php b/example/mime-override/something.php new file mode 100644 index 0000000..abb2fca --- /dev/null +++ b/example/mime-override/something.php @@ -0,0 +1 @@ + diff --git a/example/rewrite/.local-web-server.json b/example/rewrite/.local-web-server.json new file mode 100644 index 0000000..ae46592 --- /dev/null +++ b/example/rewrite/.local-web-server.json @@ -0,0 +1,7 @@ +{ + "rewrite": [ + { "from": "/css/*", "to": "/build/styles/$1" }, + { "from": "/npm/*", "to": "http://registry.npmjs.org/$1" }, + { "from": "/:user/repos/:name", "to": "https://api.github.com/repos/:user/:name" } + ] +} diff --git a/example/rewrite/build/styles/style.css b/example/rewrite/build/styles/style.css new file mode 100644 index 0000000..0a36465 --- /dev/null +++ b/example/rewrite/build/styles/style.css @@ -0,0 +1,4 @@ +body { + font-family: monospace; + font-size: 1.3em; +} diff --git a/example/rewrite/index.html b/example/rewrite/index.html new file mode 100644 index 0000000..2711101 --- /dev/null +++ b/example/rewrite/index.html @@ -0,0 +1,22 @@ + + + +

Rewriting paths

+ +

Config

+

+{
+  "rewrite": [
+    { "from": "/css/*", "to": "/build/styles/$1" },
+    { "from": "/npm/*", "to": "http://registry.npmjs.org/$1" },
+    { "from": "/:user/repos/:name", "to": "https://api.github.com/repos/:user/:name" }
+  ]
+}
+
+ +

Links

+ diff --git a/example/simple/css/style.css b/example/simple/css/style.css new file mode 100644 index 0000000..7fb71e5 --- /dev/null +++ b/example/simple/css/style.css @@ -0,0 +1,7 @@ +body { + background-color: #AA3939; + color: #FFE2E2 +} +svg { + fill: #000 +} diff --git a/example/simple/index.html b/example/simple/index.html new file mode 100644 index 0000000..008f97d --- /dev/null +++ b/example/simple/index.html @@ -0,0 +1,10 @@ + + + +

Amazing Page

+

+ With a freaky triangle.. +

+ + + diff --git a/example/simple/package.json b/example/simple/package.json new file mode 100644 index 0000000..03e697a --- /dev/null +++ b/example/simple/package.json @@ -0,0 +1,7 @@ +{ + "name": "example", + "version": "1.0.0", + "local-web-server": { + "port": 8100 + } +} diff --git a/example/spa/.local-web-server.json b/example/spa/.local-web-server.json new file mode 100644 index 0000000..2c63606 --- /dev/null +++ b/example/spa/.local-web-server.json @@ -0,0 +1,3 @@ +{ + "spa": "index.html" +} diff --git a/example/spa/css/style.css b/example/spa/css/style.css new file mode 100644 index 0000000..6d3a1e2 --- /dev/null +++ b/example/spa/css/style.css @@ -0,0 +1,3 @@ +body { + background-color: IndianRed; +} diff --git a/example/spa/index.html b/example/spa/index.html new file mode 100644 index 0000000..c2d71a9 --- /dev/null +++ b/example/spa/index.html @@ -0,0 +1,8 @@ + + + +

Single Page App

+

Location:

+ diff --git a/jsdoc2md/README.hbs b/jsdoc2md/README.hbs new file mode 100644 index 0000000..8de83d8 --- /dev/null +++ b/jsdoc2md/README.hbs @@ -0,0 +1,210 @@ +[![view on npm](http://img.shields.io/npm/v/local-web-server.svg)](https://www.npmjs.org/package/local-web-server) +[![npm module downloads](http://img.shields.io/npm/dt/local-web-server.svg)](https://www.npmjs.org/package/local-web-server) +[![Build Status](https://travis-ci.org/75lb/local-web-server.svg?branch=master)](https://travis-ci.org/75lb/local-web-server) +[![Dependency Status](https://david-dm.org/75lb/local-web-server.svg)](https://david-dm.org/75lb/local-web-server) +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](https://github.com/feross/standard) + +# local-web-server +A simple web-server for productive front-end development. + +**Requires node v4.0.0 or higher**. + +## Synopsis +For the examples below, we assume we're in a project directory looking like this: + +```sh +. +├── css +│   └── style.css +├── index.html +└── package.json +``` + +All paths/routes are specified using [express syntax](http://expressjs.com/guide/routing.html#route-paths). + +### Static site + +Fire up your static site on the default port: +```sh +$ ws +serving at http://localhost:8000 +``` + +### Single Page Application + +You're building a web app with client-side routing, so mark `index.html` as the SPA. +```sh +$ ws --spa index.html +``` + +By default, typical SPA urls (e.g. `/user/1`, `/login`) would return `404 Not Found` as a file does not exist with that path. By marking `index.html` as the SPA you create this rule: + +*If a static file at the requested path exists (e.g. `/css/style.css`) then serve it, if it does not (e.g. `/login`) then serve the specified SPA and handle the route client-side.* + +### Access Control + +By default, access to all files is allowed (including dot files). Use `--forbid` to establish a blacklist: +```sh +$ ws --forbid '*.json' '*.yml' +serving at http://localhost:8000 +``` + +### URL rewriting + +Your application requested `/css/style.css` but it's stored at `/build/css/style.css`. To avoid a 404 you need a rewrite rule: + +```sh +$ ws --rewrite '/css/style.css -> /build/css/style.css' +``` + +Or, more generally (matching any stylesheet under `/css`): + +```sh +$ ws --rewrite '/css/:stylesheet -> /build/css/:stylesheet' +``` + +With a deep CSS directory structure it may be easier to mount the entire contents of `/build/css` to the `/css` path: + +```sh +$ ws --rewrite '/css/* -> /build/css/$1' +``` + +this rewrites `/css/a` as `/build/css/a`, `/css/a/b/c` as `/build/css/a/b/c` etc. + + +#### Proxied requests + +If the `to` URL contains a remote host, local-web-server will act as a proxy - fetching and responding with the remote resource. + +Mount the npm registry locally: +```sh +$ ws --rewrite '/npm/* -> http://registry.npmjs.org/$1' +``` + +Map local requests for repo data to the Github API: +```sh +$ ws --rewrite '/:user/repos/:name -> https://api.github.com/repos/:user/:name' +``` + +### Stored config + +Use the same port and blacklist every time? Persist it to `package.json`: +```json +{ + "name": "example", + "version": "1.0.0", + "local-web-server": { + "port": 8100, + "forbid": "*.json" + } +} +``` + +or `.local-web-server.json` +```json +{ + "port": 8100, + "forbid": "*.json" +} +``` + +local-web-server will merge and use all config found, searching from the current directory upward. In the case both `package.json` and `.local-web-server.json` config is found in the same directory, `.local-web-server.json` will take precedence. Command-line options take precedence over all. + +To inspect stored config, run: +```sh +$ ws --config +``` + +### Logging +By default, local-web-server outputs a simple, dynamic statistics view. To see traditional web server logs, use `--log-format`: + +```sh +$ ws --log-format combined +serving at http://localhost:8000 +::1 - - [16/Nov/2015:11:16:52 +0000] "GET / HTTP/1.1" 200 12290 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2562.0 Safari/537.36" +``` + +The format value supplied is passed directly to [morgan](https://github.com/expressjs/morgan). The exception is `--log-format none` which disables all output. + +### Other usage + +#### Compression + +Serve gzip-compressed resources, where applicable +```sh +$ ws --compress +``` + +#### Disable caching + +Disable etag response headers, forcing resources to be served in full every time. +```sh +$ ws --no-cache +``` + +#### mime-types +You can set additional mime-type/extension mappings, or override the defaults by setting a `mime` value in the stored config. This value is passed directly to [mime.define()](https://github.com/broofa/node-mime#mimedefine). Example: + +```json +{ + "mime": { + "text/plain": [ "php", "pl" ] + } +} +``` + +#### Log Visualisation +Instructions for how to visualise log output using goaccess, logstalgia or gltail [here](https://github.com/75lb/local-web-server/blob/master/doc/visualisation.md). + +## Install +Ensure [node.js](http://nodejs.org) is installed first. Linux/Mac users may need to run the following commands with `sudo`. + +```sh +$ npm install -g local-web-server +``` + +This will install the `ws` tool globally. To see the available options, run: +```sh +$ ws --help +``` + +## Distribute with your project +The standard convention with client-server applications is to add an `npm start` command to launch the server component. + +1\. Install the server as a dev dependency + +```sh +$ npm install local-web-server --save-dev +``` + +2\. Add a `start` command to your `package.json`: + +```json +{ + "name": "example", + "version": "1.0.0", + "local-web-server": { + "port": 8100, + "forbid": "\\.json$" + }, + "scripts": { + "start": "ws" + } +} +``` + +3\. Document how to build and launch your site + +```sh +$ npm install +$ npm start +serving at http://localhost:8100 +``` + +## API Reference + +{{>main}} + +* * * + +© 2015 Lloyd Brookes <75pound@gmail.com>. Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown). diff --git a/lib/cli-options.js b/lib/cli-options.js index 7a3657d..ca0b02e 100644 --- a/lib/cli-options.js +++ b/lib/cli-options.js @@ -1,30 +1,54 @@ -module.exports = [ - { - name: 'port', alias: 'p', type: Number, defaultOption: true, - description: 'Web server port', group: 'server' - }, - { - name: 'log-format', alias: 'f', type: String, - description: "If a format is supplied an access log is written to stdout. If not, a statistics view is displayed. Use a preset ('none', 'dev','combined', 'short', 'tiny' or 'logstalgia') or supply a custom format (e.g. ':method -> :url').", group: 'server' - }, - { - name: 'directory', alias: 'd', type: String, - description: 'Root directory, defaults to the current directory', group: 'server' - }, - { - name: 'compress', alias: 'c', type: Boolean, - description: 'Enable gzip compression, reduces bandwidth.', group: 'server' - }, - { - name: 'refresh-rate', alias: 'r', type: Number, - description: 'Statistics view refresh rate in ms. Defaults to 500.', group: 'server' - }, - { - name: 'help', alias: 'h', type: Boolean, - description: 'Print these usage instructions', group: 'misc' - }, - { - name: 'config', type: Boolean, - description: 'Print the stored config', group: 'misc' +module.exports = { + definitions: [ + { + name: 'port', alias: 'p', type: Number, defaultOption: true, + description: 'Web server port', group: 'server' + }, + { + name: 'log-format', alias: 'f', type: String, + description: "If a format is supplied an access log is written to stdout. If not, a dynamic statistics view is displayed. Use a preset ('none', 'dev','combined', 'short', 'tiny' or 'logstalgia') or supply a custom format (e.g. ':method -> :url').", group: 'server' + }, + { + name: 'directory', alias: 'd', type: String, + description: 'Root directory, defaults to the current directory', group: 'server' + }, + { + name: 'compress', alias: 'c', type: Boolean, + description: 'Enable gzip compression, reduces bandwidth.', group: 'server' + }, + { + name: 'forbid', alias: 'b', type: String, multiple: true, typeLabel: '[underline]{regexp} ...', + description: 'A list of forbidden routes', group: 'server' + }, + { + name: 'no-cache', alias: 'n', type: Boolean, + description: 'Disable etag-based caching - forces loading from disk each request.', group: 'server' + }, + { + name: 'rewrite', alias: 'r', type: String, multiple: true, typeLabel: '[underline]{expression} ...', + description: 'A list of URL rewrite rules', group: 'server' + }, + { + name: 'help', alias: 'h', type: Boolean, + description: 'Print these usage instructions', group: 'misc' + }, + { + name: 'config', type: Boolean, + description: 'Print the config found in [underline]{package.json} and/or [underline]{.local-web-server}', group: 'misc' + } + ], + usageData: { + title: 'local-web-server', + description: 'A simple web-server for productive front-end development.', + footer: 'Project home: [underline]{https://github.com/75lb/local-web-server}', + synopsis: [ + '$ ws [server options]', + '$ ws --config', + '$ ws --help' + ], + groups: { + server: 'Server', + misc: 'Misc' + } } -] +} diff --git a/lib/local-web-server.js b/lib/local-web-server.js new file mode 100644 index 0000000..db94de2 --- /dev/null +++ b/lib/local-web-server.js @@ -0,0 +1,193 @@ +'use strict' +const path = require('path') +const http = require('http') +const url = require('url') +const Koa = require('koa') +const convert = require('koa-convert') +const cors = require('kcors') +const _ = require('koa-route') +const pathToRegexp = require('path-to-regexp') + +/** + * @module local-web-server + */ +module.exports = localWebServer + +/** + * Returns a Koa application + * + * @param [options] {object} - options + * @param [options.static] {object} - koajs/static config + * @param [options.static.root] {string} - root directory + * @param [options.static.options] {string} - options + * @param [options.serveIndex] {object} - koa-serve-index config + * @param [options.serveIndex.path] {string} - root directory + * @param [options.serveIndex.options] {string} - options + * @param [options.forbid] {string[]} - a list of forbidden routes. + * + * @alias module:local-web-server + * @example + * const localWebServer = require('local-web-server') + * localWebServer().listen(8000) + */ +function localWebServer (options) { + options = Object.assign({ + static: {}, + serveIndex: {}, + log: {}, + compress: false, + mime: {}, + forbid: [], + rewrite: [] + }, 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)) + + /* CORS: allow from any origin */ + app.use(cors()) + + /* rewrite rules */ + if (options.rewrite && options.rewrite.length) { + options.rewrite.forEach(route => { + if (route.to) { + if (url.parse(route.to).host) { + app.use(_.all(route.from, proxyRequest(route))) + } else { + const rewrite = require('koa-rewrite') + app.use(rewrite(route.from, route.to)) + } + } + }) + } + + /* path blacklist */ + if (options.forbid.length) { + app.use(blacklist(options.forbid)) + } + + /* Cache */ + if (!options['no-cache']) { + const conditional = require('koa-conditional-get') + const etag = require('koa-etag') + app.use(conditional()) + app.use(etag()) + } + + /* mime-type overrides */ + if (options.mime) { + app.use((ctx, next) => { + return next().then(() => { + const reqPathExtension = path.extname(ctx.path).slice(1) + Object.keys(options.mime).forEach(mimeType => { + const extsToOverride = options.mime[mimeType] + if (extsToOverride.indexOf(reqPathExtension) > -1) ctx.type = mimeType + }) + }) + }) + } + + /* compress response */ + if (options.compress) { + const compress = require('koa-compress') + app.use(compress()) + } + + /* Logging */ + if (log.format !== 'none') { + const morgan = require('koa-morgan') + + if (!log.format) { + const streamLogStats = require('stream-log-stats') + log.options.stream = streamLogStats({ refreshRate: 500 }) + app.use(morgan.middleware('common', log.options)) + } else if (log.format === 'logstalgia') { + morgan.token('date', logstalgiaDate) + app.use(morgan.middleware('combined', log.options)) + } else { + app.use(morgan.middleware(log.format, log.options)) + } + } + + /* serve static files */ + if (options.static.root) { + const serve = require('koa-static') + app.use(serve(options.static.root, options.static.options)) + } + + /* serve directory index */ + if (options.serveIndex.path) { + const serveIndex = require('koa-serve-index') + app.use(serveIndex(options.serveIndex.path, options.serveIndex.options)) + } + + /* for any URL not matched by static (e.g. `/search`), serve the SPA */ + if (options.spa) { + const send = require('koa-send') + app.use(_.all('*', function * () { + yield send(this, options.spa, { root: process.cwd() }) + })) + } + return app +} + +function logstalgiaDate () { + var d = new Date() + return (`${d.getDate()}/${d.getUTCMonth()}/${d.getFullYear()}:${d.toTimeString()}`).replace('GMT', '').replace(' (BST)', '') +} + +function proxyRequest (route) { + const httpProxy = require('http-proxy') + const proxy = httpProxy.createProxyServer({ + changeOrigin: true + }) + + return function proxyMiddleware (ctx) { + const next = arguments[arguments.length - 1] + const keys = [] + route.re = pathToRegexp(route.from, keys) + route.new = ctx.path.replace(route.re, route.to) + + keys.forEach((key, index) => { + const re = RegExp(`:${key.name}`, 'g') + route.new = route.new + .replace(re, arguments[index + 1] || '') + }) + + /* test no keys remain in the new path */ + keys.length = 0 + pathToRegexp(route.new, keys) + if (keys.length) { + ctx.throw(500, `[PROXY] Invalid target URL: ${route.new}`) + return next() + } + + ctx.response = false + + proxy.once('error', err => { + ctx.throw(500, `[PROXY] ${err.message}: ${route.new}`) + }) + proxy.once('proxyReq', function (proxyReq) { + proxyReq.path = url.parse(route.new).path; + }) + proxy.web(ctx.req, ctx.res, { target: route.new }) + } +} + +function blacklist (forbid) { + return function blacklist (ctx, next) { + if (forbid.some(expression => pathToRegexp(expression).test(ctx.path))) { + ctx.throw(403, http.STATUS_CODES[403]) + } else { + return next() + } + } +} + +process.on('unhandledRejection', (reason, p) => { + throw reason +}) diff --git a/package.json b/package.json index 996f7e1..2de092d 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,54 @@ { "name": "local-web-server", "version": "0.5.23", - "description": "Lightweight static web server, zero configuration. Perfect for front-end devs.", + "description": "A simple web-server for productive front-end development", "bin": { - "ws": "./bin/ws.js" + "ws": "./bin/cli.js" }, + "main": "lib/local-web-server.js", "license": "MIT", + "keywords": [ + "dev", + "server", + "web", + "tool", + "front-end", + "development", + "cors", + "mime", + "rest" + ], "engines": { - "node": ">=0.10.0" + "node": ">=4.0.0" }, "scripts": { - "lint": "jshint bin/ws.js; echo;" + "test": "tape test/*.js", + "docs": "jsdoc2md -t jsdoc2md/README.hbs -p list lib/*.js > README.md; echo", + "cover": "istanbul cover ./node_modules/.bin/tape test/*.js && cat coverage/lcov.info | coveralls && rm -rf coverage; echo" }, "repository": "https://github.com/75lb/local-web-server", - "author": "Lloyd Brookes", + "author": "Lloyd Brookes <75pound@gmail.com>", "dependencies": { - "command-line-args": "^1.0.0", - "compression": "^1.0.2", - "config-master": "^1", - "connect": "^3.0.0", - "console-dope": "~0.3.0", - "home-path": "^1", - "morgan": "^1.0.0", - "object-tools": "^2", - "proxy-middleware": "^0.13.0", - "serve-index": "^1.6.3", - "serve-static": "^1.8", - "stream-log-stats": "^1", - "typical": "^2.0.0" + "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-compress": "^1.0.8", + "koa-conditional-get": "^1.0.3", + "koa-convert": "^1.1.0", + "koa-etag": "^2.1.0", + "koa-morgan": "^0.4.0", + "koa-rewrite": "^1.1.1", + "koa-route": "^3", + "koa-send": "^3.1.0", + "koa-serve-index": "^1.1.0", + "koa-static": "^1.5.2", + "path-to-regexp": "^1.2.1", + "stream-log-stats": "^v1.1.0-0" }, - "local-web-server": { - "mime": { - "text/plain": [ - "php" - ] - } + "devDependencies": { + "req-then": "^0.2.2", + "tape": "^4.2.2" } } diff --git a/test/.local-web-server.json b/test/.local-web-server.json deleted file mode 100644 index 611caff..0000000 --- a/test/.local-web-server.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "mime": { - "text/plain": [ "php" ] - } -} diff --git a/test/ajax.html b/test/fixture/ajax.html similarity index 83% rename from test/ajax.html rename to test/fixture/ajax.html index ee6bbce..3430c61 100644 --- a/test/ajax.html +++ b/test/fixture/ajax.html @@ -9,10 +9,10 @@ - \ No newline at end of file + diff --git a/test/fixture/big-file.txt b/test/fixture/big-file.txt new file mode 100644 index 0000000..d5b9685 --- /dev/null +++ b/test/fixture/big-file.txt @@ -0,0 +1,195 @@ +[![view on npm](http://img.shields.io/npm/v/local-web-server.svg)](https://www.npmjs.org/package/local-web-server) +[![npm module downloads per month](http://img.shields.io/npm/dm/local-web-server.svg)](https://www.npmjs.org/package/local-web-server) +[![Dependency Status](https://david-dm.org/75lb/local-web-server.svg)](https://david-dm.org/75lb/local-web-server) +[![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). + +![local-web-server](http://75lb.github.io/local-web-server/ws.gif) + +## Install +Ensure [node.js](http://nodejs.org) is installed first. Linux/Mac users may need to run the following commands with `sudo`. + +### Globally +```sh +$ npm install -g local-web-server +``` + +### Bundled with your project +```sh +$ npm install local-web-server --save-dev +``` + +Then add an `start` script to your `package.json` (the standard npm approach): +```json +{ + "name": "my-web-app", + "version": "1.0.0", + "scripts": { + "start": "ws" + } +} +``` +This simplifies a rather specific-looking instruction set like: + +```sh +$ npm install +$ npm install -g local-web-server +$ ws +``` + +to the following, server implementation and launch details abstracted away: +```sh +$ npm install +$ npm start +``` + +## Usage +``` +Usage +$ ws +$ ws --config +$ ws --help + +Server +-p, --port Web server port +-f, --log-format If a format is supplied an access log is written to stdout. If not, a statistics view is displayed. Use a + preset ('none', 'dev','combined', 'short', 'tiny' or 'logstalgia') or supply a custom format (e.g. ':method -> + :url'). +-d, --directory Root directory, defaults to the current directory +-c, --compress Enable gzip compression, reduces bandwidth. +-r, --refresh-rate Statistics view refresh rate in ms. Defaults to 500. + +Misc +-h, --help Print these usage instructions +--config Print the stored config +``` + +From the folder you wish to serve, run: +```sh +$ ws +serving at http://localhost:8000 +``` + +If you wish to serve a different directory, run: +```sh +$ ws -d ~/mysite/ +serving /Users/Lloyd/mysite at http://localhost:8000 +``` + +If you wish to override the default port (8000), use `--port` or `-p`: +```sh +$ ws --port 9000 +serving at http://localhost:9000 +``` + +To add compression, reducing bandwidth, increasing page load time (by 10-15% on my Macbook Air) +```sh +$ ws --compress +``` + +### Logging +Passing a value to `--log-format` will write an access log to `stdout`. + +Either use a built-in [morgan](https://github.com/expressjs/morgan) logger preset: +```sh +$ ws --log-format short +``` + +Or a custom [morgan](https://github.com/expressjs/morgan) log format: +```sh +$ ws -f ':method -> :url' +``` + +Or silence: +```sh +$ ws -f none +``` + +## Storing default options +To store per-project options, saving you the hassle of inputting them everytime, store them in the `local-web-server` property of your project's `package.json`: +```json +{ + "name": "my-project", + "version": "0.11.8", + "local-web-server":{ + "port": 8100 + } +} +``` + +Or in a `.local-web-server.json` file stored in the directory you want to serve (typically the root folder of your site): +```json +{ + "port": 8100, + "log-format": "tiny" +} +``` + +Or store global defaults in a `.local-web-server.json` file in your home directory. +```json +{ + "port": 3000, + "refresh-rate": 1000 +} +``` + +All stored defaults are overriden by options supplied at the command line. + +To view your stored defaults, run: + +```sh +$ ws --config +``` + +## mime-types +You can set additional mime-type/extension mappings, or override the defaults by setting a `mime` value in your local config. This value is passed directly to [mime.define()](https://github.com/broofa/node-mime#mimedefine). Example: + +```json +{ + "mime": { + "text/plain": [ "php", "pl" ] + } +} +``` + +## Use with Logstalgia +local-web-server is compatible with [logstalgia](http://code.google.com/p/logstalgia/). + +### Install Logstalgia +On MacOSX, install with [homebrew](http://brew.sh): +```sh +$ brew install logstalgia +``` + +Alternatively, [download a release for your system from github](https://github.com/acaudwell/Logstalgia/releases/latest). + +Then pipe the `logstalgia` output format directly into logstalgia for real-time visualisation: +```sh +$ ws -f logstalgia | logstalgia - +``` + +![local-web-server with logstalgia](http://75lb.github.io/local-web-server/logstagia.gif) + +## Use with glTail +To use with [glTail](http://www.fudgie.org), write your log to disk using the "default" format: +```sh +$ ws -f default > web.log +``` + +Then specify this file in your glTail config: + +```yaml +servers: + dev: + host: localhost + source: local + files: /Users/Lloyd/Documents/MySite/web.log + parser: apache + color: 0.2, 0.2, 1.0, 1.0 +``` + +* * * + +© 2015 Lloyd Brookes <75pound@gmail.com> diff --git a/test/fixture/file.txt b/test/fixture/file.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/test/fixture/file.txt @@ -0,0 +1 @@ +test diff --git a/test/fixture/forbid/one.html b/test/fixture/forbid/one.html new file mode 100644 index 0000000..5626abf --- /dev/null +++ b/test/fixture/forbid/one.html @@ -0,0 +1 @@ +one diff --git a/test/fixture/forbid/two.php b/test/fixture/forbid/two.php new file mode 100644 index 0000000..abb2fca --- /dev/null +++ b/test/fixture/forbid/two.php @@ -0,0 +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/fixture/rewrite/one.html b/test/fixture/rewrite/one.html new file mode 100644 index 0000000..5626abf --- /dev/null +++ b/test/fixture/rewrite/one.html @@ -0,0 +1 @@ +one diff --git a/test/fixture/something.php b/test/fixture/something.php new file mode 100644 index 0000000..abb2fca --- /dev/null +++ b/test/fixture/something.php @@ -0,0 +1 @@ + diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..51d767f --- /dev/null +++ b/test/test.js @@ -0,0 +1,147 @@ +'use strict' +const test = require('tape') +const request = require('req-then') +const localWebServer = require('../') +const http = require('http') +const PassThrough = require('stream').PassThrough + +function launchServer (app, options) { + options = options || {} + const path = `http://localhost:8100${options.path || '/'}` + const server = http.createServer(app.callback()) + return server.listen(8100, () => { + const req = request(path, options.reqOptions) + if (options.onSuccess) req.then(options.onSuccess) + if (!options.leaveOpen) req.then(() => server.close()) + req.catch(err => console.error('LAUNCH ERROR', err.stack)) + }) +} + +test('static', function (t) { + t.plan(1) + const app = localWebServer({ + log: { format: 'none' }, + static: { + root: __dirname + '/fixture', + options: { + index: 'file.txt' + } + } + }) + launchServer(app, { onSuccess: response => { + t.strictEqual(response.data, 'test\n') + }}) +}) + +test('serve-index', function (t) { + t.plan(2) + const app = localWebServer({ + log: { format: 'none' }, + serveIndex: { + path: __dirname + '/fixture', + options: { + icons: true + } + } + }) + launchServer(app, { onSuccess: response => { + t.ok(/listing directory/.test(response.data)) + t.ok(/class="icon/.test(response.data)) + }}) +}) + +test('log: common', function (t) { + t.plan(1) + const stream = PassThrough() + + stream.on('readable', () => { + let chunk = stream.read() + if (chunk) t.ok(/GET/.test(chunk.toString())) + }) + + const app = localWebServer({ + log: { + format: 'common', + options: { + stream: stream + } + } + }) + launchServer(app) +}) + +test('compress', function(t){ + t.plan(1) + const app = localWebServer({ + compress: true, + log: { format: 'none' }, + static: { root: __dirname + '/fixture' } + }) + launchServer( + app, + { + reqOptions: { headers: { 'Accept-Encoding': 'gzip' } }, + path: '/big-file.txt', + onSuccess: response => { + t.strictEqual(response.res.headers['content-encoding'], 'gzip') + } + } + ) +}) + +test('mime', function(t){ + t.plan(2) + const app = localWebServer({ + log: { format: 'none' }, + static: { root: __dirname + '/fixture' }, + mime: { 'text/plain': [ 'php' ]} + }) + launchServer(app, { path: '/something.php', onSuccess: response => { + t.strictEqual(response.res.statusCode, 200) + t.ok(/text\/plain/.test(response.res.headers['content-type'])) + }}) +}) + +test('forbid', function (t) { + t.plan(2) + const app = localWebServer({ + log: { format: 'none' }, + static: { root: __dirname + '/fixture/forbid' }, + forbid: [ '*.php', '*.html' ] + }) + const server = launchServer(app, { leaveOpen: true }) + request('http://localhost:8100/two.php') + .then(response => { + t.strictEqual(response.res.statusCode, 403) + request('http://localhost:8100/one.html') + .then(response => { + t.strictEqual(response.res.statusCode, 403) + server.close() + }) + }) +}) + +test('rewrite: local', function(t){ + t.plan(1) + const app = localWebServer({ + log: { format: 'none' }, + static: { root: __dirname + '/fixture/rewrite' }, + rewrite: [ { from: '/two.html', to: '/one.html'} ] + }) + launchServer(app, { path: '/two.html', onSuccess: response => { + t.strictEqual(response.data, 'one\n') + }}) +}) + +test('rewrite: proxy', function(t){ + t.plan(2) + const app = localWebServer({ + log: { format: 'none' }, + static: { root: __dirname + '/fixture/rewrite' }, + rewrite: [ { from: '/test/*', to: 'http://registry.npmjs.org/$1'} ] + }) + launchServer(app, { path: '/test/', onSuccess: response => { + t.strictEqual(response.res.statusCode, 200) + t.ok(/db_name/.test(response.data)) + }}) +})