Merge branch 'next'
This commit is contained in:
1
.coveralls.yml
Normal file
1
.coveralls.yml
Normal file
@ -0,0 +1 @@
|
||||
repo_token: w9HmlMl9558e1LpP9p62YgYutkVE9PqtN
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
node_modules
|
||||
tmp
|
||||
|
18
.jshintrc
18
.jshintrc
@ -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
|
||||
}
|
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '5.0'
|
||||
- '4.2'
|
276
README.md
276
README.md
@ -1,193 +1,231 @@
|
||||
[](https://www.npmjs.org/package/local-web-server)
|
||||
[](https://www.npmjs.org/package/local-web-server)
|
||||
[](https://www.npmjs.org/package/local-web-server)
|
||||
[](https://travis-ci.org/75lb/local-web-server)
|
||||
[](https://david-dm.org/75lb/local-web-server)
|
||||
[](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.
|
||||
|
||||

|
||||
**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`.
|
||||
|
||||
### 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:
|
||||
## Synopsis
|
||||
For the examples below, we assume we're in a project directory looking like this:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
$ npm install -g local-web-server
|
||||
$ ws
|
||||
.
|
||||
├── css
|
||||
│ └── style.css
|
||||
├── index.html
|
||||
└── package.json
|
||||
```
|
||||
|
||||
to the following, server implementation and launch details abstracted away:
|
||||
```sh
|
||||
$ npm install
|
||||
$ npm start
|
||||
```
|
||||
All paths/routes are specified using [express syntax](http://expressjs.com/guide/routing.html#route-paths).
|
||||
|
||||
## Usage
|
||||
```
|
||||
Usage
|
||||
$ ws <server options>
|
||||
$ ws --config
|
||||
$ ws --help
|
||||
### Static site
|
||||
|
||||
Server
|
||||
-p, --port <number> Web server port
|
||||
-f, --log-format <string> 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 <string> Root directory, defaults to the current directory
|
||||
-c, --compress Enable gzip compression, reduces bandwidth.
|
||||
-r, --refresh-rate <number> 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:
|
||||
Fire up your static site on the default port:
|
||||
```sh
|
||||
$ ws
|
||||
serving at http://localhost:8000
|
||||
```
|
||||
|
||||
If you wish to serve a different directory, run:
|
||||
### Single Page Application
|
||||
|
||||
You're building a web app with client-side routing, so mark `index.html` as the SPA.
|
||||
```sh
|
||||
$ ws -d ~/mysite/
|
||||
serving /Users/Lloyd/mysite at http://localhost:8000
|
||||
$ ws --spa index.html
|
||||
```
|
||||
|
||||
If you wish to override the default port (8000), use `--port` or `-p`:
|
||||
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 --port 9000
|
||||
serving at http://localhost:9000
|
||||
$ ws --forbid '*.json' '*.yml'
|
||||
serving at http://localhost:8000
|
||||
```
|
||||
|
||||
To add compression, reducing bandwidth, increasing page load time (by 10-15% on my Macbook Air)
|
||||
### 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 --compress
|
||||
$ ws --rewrite '/css/style.css -> /build/css/style.css'
|
||||
```
|
||||
|
||||
### Logging
|
||||
Passing a value to `--log-format` will write an access log to `stdout`.
|
||||
Or, more generally (matching any stylesheet under `/css`):
|
||||
|
||||
Either use a built-in [morgan](https://github.com/expressjs/morgan) logger preset:
|
||||
```sh
|
||||
$ ws --log-format short
|
||||
$ ws --rewrite '/css/:stylesheet -> /build/css/:stylesheet'
|
||||
```
|
||||
|
||||
Or a custom [morgan](https://github.com/expressjs/morgan) log format:
|
||||
With a deep CSS directory structure it may be easier to mount the entire contents of `/build/css` to the `/css` path:
|
||||
|
||||
```sh
|
||||
$ ws -f ':method -> :url'
|
||||
$ ws --rewrite '/css/* -> /build/css/$1'
|
||||
```
|
||||
|
||||
Or silence:
|
||||
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 none
|
||||
$ ws --rewrite '/npm/* -> http://registry.npmjs.org/$1'
|
||||
```
|
||||
|
||||
## 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`:
|
||||
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": "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
|
||||
}
|
||||
```
|
||||
|
||||
All stored defaults are overriden by options supplied at the command line.
|
||||
|
||||
To view your stored defaults, run:
|
||||
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
|
||||
```
|
||||
|
||||
## 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:
|
||||
### 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" ]
|
||||
}
|
||||
"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).
|
||||
|
||||
Then pipe the `logstalgia` output format directly into logstalgia for real-time visualisation:
|
||||
This will install the `ws` tool globally. To see the available options, run:
|
||||
```sh
|
||||
$ ws -f logstalgia | logstalgia -
|
||||
$ 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
|
||||
|
||||
## 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 local-web-server --save-dev
|
||||
```
|
||||
|
||||
Then specify this file in your glTail config:
|
||||
2\. Add a `start` command to your `package.json`:
|
||||
|
||||
```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
|
||||
```json
|
||||
{
|
||||
"name": "example",
|
||||
"version": "1.0.0",
|
||||
"local-web-server": {
|
||||
"port": 8100,
|
||||
"forbid": "\\.json$"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "ws"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
© 2015 Lloyd Brookes <75pound@gmail.com>
|
||||
3\. Document how to build and launch your site
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
$ npm start
|
||||
serving at http://localhost:8100
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
<a name="module_local-web-server"></a>
|
||||
## local-web-server
|
||||
<a name="exp_module_local-web-server--localWebServer"></a>
|
||||
### localWebServer([options]) ⏏
|
||||
Returns a Koa application
|
||||
|
||||
**Kind**: Exported function
|
||||
**Params**
|
||||
- [options] <code>object</code> - options
|
||||
- [.static] <code>object</code> - koajs/static config
|
||||
- [.root] <code>string</code> - root directory
|
||||
- [.options] <code>string</code> - options
|
||||
- [.serveIndex] <code>object</code> - koa-serve-index config
|
||||
- [.path] <code>string</code> - root directory
|
||||
- [.options] <code>string</code> - options
|
||||
- [.forbid] <code>Array.<string></code> - a list of forbidden routes.
|
||||
|
||||
**Example**
|
||||
```js
|
||||
const localWebServer = require('local-web-server')
|
||||
localWebServer().listen(8000)
|
||||
```
|
||||
|
||||
* * *
|
||||
|
||||
© 2015 Lloyd Brookes <75pound@gmail.com>. Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown).
|
||||
|
99
bin/cli.js
Executable file
99
bin/cli.js
Executable file
@ -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]
|
||||
}
|
||||
})
|
||||
}
|
155
bin/ws.js
155
bin/ws.js
@ -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 <server options>',
|
||||
'$ 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'] }) })
|
||||
}
|
||||
}
|
BIN
doc/img/logstagia.gif
Normal file
BIN
doc/img/logstagia.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 570 KiB |
37
doc/visualisation.md
Normal file
37
doc/visualisation.md
Normal file
@ -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 -
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
```
|
5
example/forbid/.local-web-server.json
Normal file
5
example/forbid/.local-web-server.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"forbid": [
|
||||
"/admin/*", "*.php"
|
||||
]
|
||||
}
|
1
example/forbid/admin/blocked.html
Normal file
1
example/forbid/admin/blocked.html
Normal file
@ -0,0 +1 @@
|
||||
<h1>Forbidden page</h1>
|
1
example/forbid/allowed.html
Normal file
1
example/forbid/allowed.html
Normal file
@ -0,0 +1 @@
|
||||
<h1>A permitted page</h1>
|
5
example/forbid/index.html
Normal file
5
example/forbid/index.html
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>Forbidden routes</h1>
|
||||
|
||||
<p>
|
||||
Notice you can access <a href="allowed.html">this page</a>, but not <a href="admin/blocked.html">this admin page</a> or <a href="something.php">php file</a>.
|
||||
</p>
|
5
example/mime-override/.local-web-server.json
Normal file
5
example/mime-override/.local-web-server.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"mime": {
|
||||
"text/plain": [ "php" ]
|
||||
}
|
||||
}
|
1
example/mime-override/something.php
Normal file
1
example/mime-override/something.php
Normal file
@ -0,0 +1 @@
|
||||
<?php echo "i'm coding PHP templatez!\n" ?>
|
7
example/rewrite/.local-web-server.json
Normal file
7
example/rewrite/.local-web-server.json
Normal file
@ -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" }
|
||||
]
|
||||
}
|
4
example/rewrite/build/styles/style.css
Normal file
4
example/rewrite/build/styles/style.css
Normal file
@ -0,0 +1,4 @@
|
||||
body {
|
||||
font-family: monospace;
|
||||
font-size: 1.3em;
|
||||
}
|
22
example/rewrite/index.html
Normal file
22
example/rewrite/index.html
Normal file
@ -0,0 +1,22 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<h1>Rewriting paths</h1>
|
||||
|
||||
<h2>Config</h2>
|
||||
<pre><code>
|
||||
{
|
||||
"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" }
|
||||
]
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h2>Links</h2>
|
||||
<ul>
|
||||
<li><a href="/css/style.css">/css/style.css</li>
|
||||
<li><a href="/npm/local-web-server">/npm/local-web-server</a></li>
|
||||
<li><a href="/75lb/repos/work">/75lb/repos/work</a></li>
|
||||
</ul>
|
7
example/simple/css/style.css
Normal file
7
example/simple/css/style.css
Normal file
@ -0,0 +1,7 @@
|
||||
body {
|
||||
background-color: #AA3939;
|
||||
color: #FFE2E2
|
||||
}
|
||||
svg {
|
||||
fill: #000
|
||||
}
|
10
example/simple/index.html
Normal file
10
example/simple/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<h1>Amazing Page</h1>
|
||||
<p>
|
||||
With a freaky triangle..
|
||||
</p>
|
||||
<svg width="500" height="500">
|
||||
<polygon points="250,0 0,500 500,500"></polygon>
|
||||
</svg>
|
7
example/simple/package.json
Normal file
7
example/simple/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "example",
|
||||
"version": "1.0.0",
|
||||
"local-web-server": {
|
||||
"port": 8100
|
||||
}
|
||||
}
|
3
example/spa/.local-web-server.json
Normal file
3
example/spa/.local-web-server.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"spa": "index.html"
|
||||
}
|
3
example/spa/css/style.css
Normal file
3
example/spa/css/style.css
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
background-color: IndianRed;
|
||||
}
|
8
example/spa/index.html
Normal file
8
example/spa/index.html
Normal file
@ -0,0 +1,8 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<h1>Single Page App</h1>
|
||||
<h2>Location: <span></span></h2>
|
||||
<script>
|
||||
document.querySelector('h2 span').textContent = window.location.pathname
|
||||
</script>
|
210
jsdoc2md/README.hbs
Normal file
210
jsdoc2md/README.hbs
Normal file
@ -0,0 +1,210 @@
|
||||
[](https://www.npmjs.org/package/local-web-server)
|
||||
[](https://www.npmjs.org/package/local-web-server)
|
||||
[](https://travis-ci.org/75lb/local-web-server)
|
||||
[](https://david-dm.org/75lb/local-web-server)
|
||||
[](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).
|
@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
193
lib/local-web-server.js
Normal file
193
lib/local-web-server.js
Normal file
@ -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
|
||||
})
|
63
package.json
63
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"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"mime": {
|
||||
"text/plain": [ "php" ]
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<script>
|
||||
var $ = document.querySelector.bind(document);
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("get", "http://localhost:8000/README.md", true);
|
||||
req.open("get", "http://localhost:8000/big-file.txt", true);
|
||||
req.onload = function(){
|
||||
$("#readme").textContent = this.responseText;
|
||||
}
|
195
test/fixture/big-file.txt
Normal file
195
test/fixture/big-file.txt
Normal file
@ -0,0 +1,195 @@
|
||||
[](https://www.npmjs.org/package/local-web-server)
|
||||
[](https://www.npmjs.org/package/local-web-server)
|
||||
[](https://david-dm.org/75lb/local-web-server)
|
||||
[](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).
|
||||
|
||||

|
||||
|
||||
## 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 <server options>
|
||||
$ ws --config
|
||||
$ ws --help
|
||||
|
||||
Server
|
||||
-p, --port <number> Web server port
|
||||
-f, --log-format <string> 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 <string> Root directory, defaults to the current directory
|
||||
-c, --compress Enable gzip compression, reduces bandwidth.
|
||||
-r, --refresh-rate <number> 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 -
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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>
|
1
test/fixture/file.txt
Normal file
1
test/fixture/file.txt
Normal file
@ -0,0 +1 @@
|
||||
test
|
1
test/fixture/forbid/one.html
Normal file
1
test/fixture/forbid/one.html
Normal file
@ -0,0 +1 @@
|
||||
one
|
1
test/fixture/forbid/two.php
Normal file
1
test/fixture/forbid/two.php
Normal file
@ -0,0 +1 @@
|
||||
<?php echo "i'm coding PHP templatez!\n" ?>
|
1
test/fixture/one/file.txt
Normal file
1
test/fixture/one/file.txt
Normal file
@ -0,0 +1 @@
|
||||
one
|
1
test/fixture/rewrite/one.html
Normal file
1
test/fixture/rewrite/one.html
Normal file
@ -0,0 +1 @@
|
||||
one
|
1
test/fixture/something.php
Normal file
1
test/fixture/something.php
Normal file
@ -0,0 +1 @@
|
||||
<?php echo "i'm coding PHP templatez!\n" ?>
|
147
test/test.js
Normal file
147
test/test.js
Normal file
@ -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))
|
||||
}})
|
||||
})
|
Reference in New Issue
Block a user