Lloyd Brookes
8 years ago
40 changed files with 1 additions and 943 deletions
-
1.travis.yml
-
72doc/api.md
-
9doc/blacklist.md
-
32doc/distribute.md
-
59doc/https.md
-
BINdoc/img/logstagia.gif
-
69doc/logging.md
-
12doc/mime-types.md
-
241doc/mock-response.md
-
37doc/rewrite.md
-
12doc/spa.md
-
28doc/stored-config.md
-
56doc/visualisation.md
-
5example/built-in/forbid/.local-web-server.json
-
1example/built-in/forbid/admin/blocked.html
-
1example/built-in/forbid/allowed.html
-
5example/built-in/forbid/index.html
-
1example/built-in/forbid/something.php
-
5example/built-in/mime-override/.local-web-server.json
-
1example/built-in/mime-override/something.php
-
8example/built-in/mock-async/.local-web-server.json
-
11example/built-in/mock-async/mocks/delayed.js
-
58example/built-in/mock/.local-web-server.json
-
6example/built-in/mock/mocks/five.js
-
8example/built-in/mock/mocks/stream-self.js
-
41example/built-in/mock/mocks/user.js
-
32example/built-in/mock/mocks/users.js
-
5example/built-in/mock/mocks/users.json
-
37example/built-in/mock/mocks/users2.js
-
8example/built-in/rewrite/.local-web-server.json
-
4example/built-in/rewrite/build/styles/style.css
-
24example/built-in/rewrite/index.html
-
8example/built-in/rewrite/lws.config.js
-
7example/built-in/simple/css/style.css
-
10example/built-in/simple/index.html
-
7example/built-in/simple/package.json
-
4example/built-in/spa/.local-web-server.json
-
5example/built-in/spa/css/style.css
-
BINexample/built-in/spa/image.jpg
-
14example/built-in/spa/index.html
@ -1,3 +1,4 @@ |
|||||
language: node_js |
language: node_js |
||||
node_js: |
node_js: |
||||
- 7 |
- 7 |
||||
|
- 8 |
@ -1,72 +0,0 @@ |
|||||
## API Reference |
|
||||
|
|
||||
|
|
||||
* [local-web-server](#module_local-web-server) |
|
||||
* [LocalWebServer](#exp_module_local-web-server--LocalWebServer) ⇐ <code>module:middleware-stack</code> ⏏ |
|
||||
* [new LocalWebServer([options])](#new_module_local-web-server--LocalWebServer_new) |
|
||||
* _instance_ |
|
||||
* [.features](#module_local-web-server--LocalWebServer.LocalWebServer+features) : <code>Array.<Feature></code> |
|
||||
* [.options](#module_local-web-server--LocalWebServer.LocalWebServer+options) : <code>object</code> |
|
||||
* [.view](#module_local-web-server--LocalWebServer.LocalWebServer+view) : <code>View</code> |
|
||||
* [.server](#module_local-web-server--LocalWebServer.LocalWebServer+server) : <code>Server</code> |
|
||||
* [.getApplication()](#module_local-web-server--LocalWebServer+getApplication) ⇒ <code>function</code> |
|
||||
* [.getServer()](#module_local-web-server--LocalWebServer+getServer) ⇒ <code>Server</code> |
|
||||
* _inner_ |
|
||||
* [~loadStack()](#module_local-web-server--LocalWebServer..loadStack) ⇒ <code>object</code> |
|
||||
|
|
||||
<a name="exp_module_local-web-server--LocalWebServer"></a> |
|
||||
|
|
||||
### LocalWebServer ⇐ <code>module:middleware-stack</code> ⏏ |
|
||||
**Kind**: Exported class |
|
||||
**Extends:** <code>module:middleware-stack</code> |
|
||||
<a name="new_module_local-web-server--LocalWebServer_new"></a> |
|
||||
|
|
||||
#### new LocalWebServer([options]) |
|
||||
**Params** |
|
||||
|
|
||||
- [options] <code>object</code> - Server options |
|
||||
- .port} <code>number</code> - Port |
|
||||
- .stack} <code>Array.<string></code> | <code>Array.<Features></code> - Port |
|
||||
|
|
||||
<a name="module_local-web-server--LocalWebServer.LocalWebServer+features"></a> |
|
||||
|
|
||||
#### localWebServer.features : <code>Array.<Feature></code> |
|
||||
Loaded feature modules |
|
||||
|
|
||||
**Kind**: instance property of <code>[LocalWebServer](#exp_module_local-web-server--LocalWebServer)</code> |
|
||||
<a name="module_local-web-server--LocalWebServer.LocalWebServer+options"></a> |
|
||||
|
|
||||
#### localWebServer.options : <code>object</code> |
|
||||
Config |
|
||||
|
|
||||
**Kind**: instance property of <code>[LocalWebServer](#exp_module_local-web-server--LocalWebServer)</code> |
|
||||
<a name="module_local-web-server--LocalWebServer.LocalWebServer+view"></a> |
|
||||
|
|
||||
#### localWebServer.view : <code>View</code> |
|
||||
Current view. |
|
||||
|
|
||||
**Kind**: instance property of <code>[LocalWebServer](#exp_module_local-web-server--LocalWebServer)</code> |
|
||||
<a name="module_local-web-server--LocalWebServer.LocalWebServer+server"></a> |
|
||||
|
|
||||
#### localWebServer.server : <code>Server</code> |
|
||||
Node.js server |
|
||||
|
|
||||
**Kind**: instance property of <code>[LocalWebServer](#exp_module_local-web-server--LocalWebServer)</code> |
|
||||
<a name="module_local-web-server--LocalWebServer+getApplication"></a> |
|
||||
|
|
||||
#### localWebServer.getApplication() ⇒ <code>function</code> |
|
||||
Returns a middleware application suitable for passing to `http.createServer`. The application is a function with three args (req, res and next) which can be created by express, Koa or hand-rolled. |
|
||||
|
|
||||
**Kind**: instance method of <code>[LocalWebServer](#exp_module_local-web-server--LocalWebServer)</code> |
|
||||
<a name="module_local-web-server--LocalWebServer+getServer"></a> |
|
||||
|
|
||||
#### localWebServer.getServer() ⇒ <code>Server</code> |
|
||||
Returns a listening server which processes requests using the middleware supplied. |
|
||||
|
|
||||
**Kind**: instance method of <code>[LocalWebServer](#exp_module_local-web-server--LocalWebServer)</code> |
|
||||
<a name="module_local-web-server--LocalWebServer..loadStack"></a> |
|
||||
|
|
||||
#### LocalWebServer~loadStack() ⇒ <code>object</code> |
|
||||
Loads a module by either path or name. |
|
||||
|
|
||||
**Kind**: inner method of <code>[LocalWebServer](#exp_module_local-web-server--LocalWebServer)</code> |
|
@ -1,9 +0,0 @@ |
|||||
## 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 |
|
||||
``` |
|
||||
|
|
||||
[Example](https://github.com/lwsjs/local-web-server/tree/master/example/forbid). |
|
@ -1,32 +0,0 @@ |
|||||
## 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 |
|
||||
``` |
|
@ -1,59 +0,0 @@ |
|||||
## HTTPS Server |
|
||||
|
|
||||
Some modern techs (ServiceWorker, any `MediaDevices.getUserMedia()` request etc.) *must* be served from a secure origin (HTTPS). To launch an HTTPS server, supply a `--key` and `--cert` to local-web-server, for example: |
|
||||
|
|
||||
``` |
|
||||
$ ws --key localhost.key --cert localhost.crt |
|
||||
``` |
|
||||
|
|
||||
If you don't have a key and certificate it's trivial to create them. You do not need third-party verification (Verisign etc.) for development purposes. To get the green padlock in the browser, the certificate.. |
|
||||
|
|
||||
* must have a `Common Name` value matching the FQDN of the server |
|
||||
* must be verified by a Certificate Authority (but we can overrule this - see below) |
|
||||
|
|
||||
First create a certificate: |
|
||||
|
|
||||
1. Install openssl. |
|
||||
|
|
||||
`$ brew install openssl` |
|
||||
|
|
||||
2. Generate a RSA private key. |
|
||||
|
|
||||
`$ openssl genrsa -des3 -passout pass:x -out ws.pass.key 2048` |
|
||||
|
|
||||
3. Create RSA key. |
|
||||
|
|
||||
``` |
|
||||
$ openssl rsa -passin pass:x -in ws.pass.key -out ws.key |
|
||||
``` |
|
||||
|
|
||||
4. Create certificate request. The command below will ask a series of questions about the certificate owner. The most imporant answer to give is for `Common Name`, you can accept the default values for the others. **Important**: you **must** input your server's correct FQDN (`dev-server.local`, `laptop.home` etc.) into the `Common Name` field. The cert is only valid for the domain specified here. You can find out your computers host name by running the command `hostname`. For example, mine is `mba3.home`. |
|
||||
|
|
||||
`$ openssl req -new -key ws.key -out ws.csr` |
|
||||
|
|
||||
5. Generate self-signed certificate. |
|
||||
|
|
||||
`$ openssl x509 -req -days 365 -in ws.csr -signkey ws.key -out ws.crt` |
|
||||
|
|
||||
6. Clean up files we're finished with |
|
||||
|
|
||||
`$ rm ws.pass.key ws.csr` |
|
||||
|
|
||||
7. Launch HTTPS server. In iTerm, control-click the first URL (with the hostname matching `Common Name`) to launch your browser. |
|
||||
|
|
||||
``` |
|
||||
$ ws --key ws.key --cert ws.crt |
|
||||
serving at https://mba3.home:8010, https://127.0.0.1:8010, https://192.168.1.203:8010 |
|
||||
``` |
|
||||
|
|
||||
Chrome and Firefox will still complain your certificate has not been verified by a Certificate Authority. Firefox will offer you an `Add an exception` option, allowing you to ignore the warning and manually mark the certificate as trusted. In Chrome on Mac, you can manually trust the certificate another way: |
|
||||
|
|
||||
1. Open Keychain |
|
||||
2. Click File -> Import. Select the `.crt` file you created. |
|
||||
3. In the `Certificates` category, double-click the cert you imported. |
|
||||
4. In the `trust` section, underneath `when using this certificate`, select `Always Trust`. |
|
||||
|
|
||||
Now you have a valid, trusted certificate for development. |
|
||||
|
|
||||
### Built-in certificate |
|
||||
As a quick win, you can run `ws` with the `https` flag. This will launch an HTTPS server using a [built-in certificate](https://github.com/lwsjs/local-web-server/tree/master/ssl) registered to the domain 127.0.0.1. |
|
Before Width: 700 | Height: 360 | Size: 570 KiB |
@ -1,69 +0,0 @@ |
|||||
# 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. |
|
||||
|
|
||||
# Visualisation |
|
||||
|
|
||||
## Goaccess |
|
||||
To get live statistics in [goaccess](http://goaccess.io/), first create this config file at `~/.goaccessrc`: |
|
||||
|
|
||||
``` |
|
||||
time-format %T |
|
||||
date-format %d/%b/%Y |
|
||||
log.format %h %^[%d:%t %^] "%r" %s %b "%R" "%u" |
|
||||
``` |
|
||||
|
|
||||
Then, start the server, outputting `combined` format logs to disk: |
|
||||
|
|
||||
```sh |
|
||||
$ ws -f combined > web.log |
|
||||
``` |
|
||||
|
|
||||
In a separate tab, point goaccess at `web.log` and it will display statistics in real time: |
|
||||
|
|
||||
``` |
|
||||
$ goaccess -p ~/.goaccessrc -f web.log |
|
||||
``` |
|
||||
|
|
||||
## 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/lwsjs/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 |
|
||||
``` |
|
@ -1,12 +0,0 @@ |
|||||
## 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" ] |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
[Example](https://github.com/lwsjs/local-web-server/tree/master/example/mime-override). |
|
@ -1,241 +0,0 @@ |
|||||
## Mock Responses |
|
||||
|
|
||||
Mocks give you full control over the response headers and body returned to the client. They can be used to return anything from a simple html string to a resourceful REST API. Typically, they're used to mock services but can be used for anything. |
|
||||
|
|
||||
In the config, define an array called `mocks`. Each mock definition maps a <code>[route](http://expressjs.com/guide/routing.html#route-paths)</code> to a `response`. A simple home page: |
|
||||
```json |
|
||||
{ |
|
||||
"mocks": [ |
|
||||
{ |
|
||||
"route": "/", |
|
||||
"response": { |
|
||||
"body": "<h1>Welcome to the Mock Responses example</h1>" |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
Under the hood, the property values from the `response` object are written onto the underlying [koa response object](https://github.com/koajs/koa/blob/master/docs/api/response.md). You can set any valid koa response properies, for example [type](https://github.com/koajs/koa/blob/master/docs/api/response.md#responsetype-1): |
|
||||
```json |
|
||||
{ |
|
||||
"mocks": [ |
|
||||
{ |
|
||||
"route": "/", |
|
||||
"response": { |
|
||||
"type": "text/plain", |
|
||||
"body": "<h1>Welcome to the Mock Responses example</h1>" |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### Conditional Response |
|
||||
|
|
||||
To define a conditional response, set a `request` object on the mock definition. The `request` value acts as a query - the response defined will only be returned if each property of the `request` query matches. For example, return an XML response *only* if the request headers include `accept: application/xml`, else return 404 Not Found. |
|
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"mocks": [ |
|
||||
{ |
|
||||
"route": "/two", |
|
||||
"request": { "accepts": "xml" }, |
|
||||
"response": { |
|
||||
"body": "<result id='2' name='whatever' />" |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### Multiple Potential Responses |
|
||||
|
|
||||
To specify multiple potential responses, set an array of mock definitions to the `responses` property. The first response with a matching request query will be sent. In this example, the client will get one of two responses depending on the request method: |
|
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"mocks": [ |
|
||||
{ |
|
||||
"route": "/three", |
|
||||
"responses": [ |
|
||||
{ |
|
||||
"request": { "method": "GET" }, |
|
||||
"response": { |
|
||||
"body": "<h1>Mock response for 'GET' request on /three</h1>" |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
"request": { "method": "POST" }, |
|
||||
"response": { |
|
||||
"status": 400, |
|
||||
"body": { "message": "That method is not allowed." } |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### Dynamic Response |
|
||||
|
|
||||
The examples above all returned static data. To define a dynamic response, create a mock module. Specify its path in the `module` property: |
|
||||
```json |
|
||||
{ |
|
||||
"mocks": [ |
|
||||
{ |
|
||||
"route": "/four", |
|
||||
"module": "/mocks/stream-self.js" |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
Here's what the `stream-self` module looks like. The module should export a mock definition (an object, or array of objects, each with a `response` and optional `request`). In this example, the module simply streams itself to the response but you could set `body` to *any* [valid value](https://github.com/koajs/koa/blob/master/docs/api/response.md#responsebody-1). |
|
||||
```js |
|
||||
const fs = require('fs') |
|
||||
|
|
||||
module.exports = { |
|
||||
response: { |
|
||||
body: fs.createReadStream(__filename) |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### Response function |
|
||||
|
|
||||
For more power, define the response as a function. It will receive the [koa context](https://github.com/koajs/koa/blob/master/docs/api/context.md) as its first argument. Now you have full programmatic control over the response returned. |
|
||||
```js |
|
||||
module.exports = { |
|
||||
response: function (ctx) { |
|
||||
ctx.body = '<h1>I can do anything i want.</h1>' |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
If the route contains tokens, their values are passed to the response. For example, with this mock... |
|
||||
```json |
|
||||
{ |
|
||||
"mocks": [ |
|
||||
{ |
|
||||
"route": "/players/:id", |
|
||||
"module": "/mocks/players.js" |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
...the `id` value is passed to the `response` function. For example, a path of `/players/10?name=Lionel` would pass `10` to the response function. Additional, the value `Lionel` would be available on `ctx.query.name`: |
|
||||
```js |
|
||||
module.exports = { |
|
||||
response: function (ctx, id) { |
|
||||
ctx.body = `<h1>id: ${id}, name: ${ctx.query.name}</h1>` |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### RESTful Resource example |
|
||||
|
|
||||
Here's an example of a REST collection (users). We'll create two routes, one for actions on the resource collection, one for individual resource actions. |
|
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"mocks": [ |
|
||||
{ "route": "/users", "module": "/mocks/users.js" }, |
|
||||
{ "route": "/users/:id", "module": "/mocks/user.js" } |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
Define a module (`users.json`) defining seed data: |
|
||||
|
|
||||
```json |
|
||||
[ |
|
||||
{ "id": 1, "name": "Lloyd", "age": 40, "nationality": "English" }, |
|
||||
{ "id": 2, "name": "Mona", "age": 34, "nationality": "Palestinian" }, |
|
||||
{ "id": 3, "name": "Francesco", "age": 24, "nationality": "Italian" } |
|
||||
] |
|
||||
``` |
|
||||
|
|
||||
The collection module: |
|
||||
|
|
||||
```js |
|
||||
const users = require('./users.json') |
|
||||
|
|
||||
/* responses for /users */ |
|
||||
const mockResponses = [ |
|
||||
/* Respond with 400 Bad Request for PUT and DELETE - inappropriate on a collection */ |
|
||||
{ request: { method: 'PUT' }, response: { status: 400 } }, |
|
||||
{ request: { method: 'DELETE' }, response: { status: 400 } }, |
|
||||
{ |
|
||||
/* for GET requests return a subset of data, optionally filtered on 'minAge' and 'nationality' */ |
|
||||
request: { method: 'GET' }, |
|
||||
response: function (ctx) { |
|
||||
ctx.body = users.filter(user => { |
|
||||
const meetsMinAge = (user.age || 1000) >= (Number(ctx.query.minAge) || 0) |
|
||||
const requiredNationality = user.nationality === (ctx.query.nationality || user.nationality) |
|
||||
return meetsMinAge && requiredNationality |
|
||||
}) |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
/* for POST requests, create a new user and return the path to the new resource */ |
|
||||
request: { method: 'POST' }, |
|
||||
response: function (ctx) { |
|
||||
const newUser = ctx.request.body |
|
||||
users.push(newUser) |
|
||||
newUser.id = users.length |
|
||||
ctx.status = 201 |
|
||||
ctx.response.set('Location', `/users/${newUser.id}`) |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
|
|
||||
module.exports = mockResponses |
|
||||
``` |
|
||||
|
|
||||
The individual resource module: |
|
||||
|
|
||||
```js |
|
||||
const users = require('./users.json') |
|
||||
|
|
||||
/* responses for /users/:id */ |
|
||||
const mockResponses = [ |
|
||||
/* don't support POST here */ |
|
||||
{ request: { method: 'POST' }, response: { status: 400 } }, |
|
||||
|
|
||||
/* for GET requests, return a particular user */ |
|
||||
{ |
|
||||
request: { method: 'GET' }, |
|
||||
response: function (ctx, id) { |
|
||||
ctx.body = users.find(user => user.id === Number(id)) |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/* for PUT requests, update the record */ |
|
||||
{ |
|
||||
request: { method: 'PUT' }, |
|
||||
response: function (ctx, id) { |
|
||||
const updatedUser = ctx.request.body |
|
||||
const existingUserIndex = users.findIndex(user => user.id === Number(id)) |
|
||||
users.splice(existingUserIndex, 1, updatedUser) |
|
||||
ctx.status = 200 |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/* DELETE request: remove the record */ |
|
||||
{ |
|
||||
request: { method: 'DELETE' }, |
|
||||
response: function (ctx, id) { |
|
||||
const existingUserIndex = users.findIndex(user => user.id === Number(id)) |
|
||||
users.splice(existingUserIndex, 1) |
|
||||
ctx.status = 200 |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
|
|
||||
module.exports = mockResponses |
|
||||
``` |
|
||||
|
|
||||
[Example](https://github.com/lwsjs/local-web-server/tree/master/example/mock). |
|
@ -1,37 +0,0 @@ |
|||||
## 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' |
|
||||
``` |
|
||||
|
|
||||
[Example](https://github.com/lwsjs/local-web-server/tree/master/example/rewrite). |
|
@ -1,12 +0,0 @@ |
|||||
## 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 paths (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.* |
|
||||
|
|
||||
[Example](https://github.com/lwsjs/local-web-server/tree/master/example/spa). |
|
@ -1,28 +0,0 @@ |
|||||
## Stored config |
|
||||
|
|
||||
Use the same options every time? Persist then 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. Options set on the command line take precedence over all. |
|
||||
|
|
||||
To inspect stored config, run: |
|
||||
```sh |
|
||||
$ ws --config |
|
||||
``` |
|
@ -1,56 +0,0 @@ |
|||||
## Goaccess |
|
||||
To get live statistics in [goaccess](http://goaccess.io/), first create this config file at `~/.goaccessrc`: |
|
||||
|
|
||||
``` |
|
||||
time-format %T |
|
||||
date-format %d/%b/%Y |
|
||||
log.format %h %^[%d:%t %^] "%r" %s %b "%R" "%u" |
|
||||
``` |
|
||||
|
|
||||
Then, start the server, outputting `combined` format logs to disk: |
|
||||
|
|
||||
```sh |
|
||||
$ ws -f combined > web.log |
|
||||
``` |
|
||||
|
|
||||
In a separate terminal, point goaccess at `web.log` and it will display statistics in real time: |
|
||||
|
|
||||
``` |
|
||||
$ goaccess -p ~/.goaccessrc -f web.log |
|
||||
``` |
|
||||
|
|
||||
## 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/lwsjs/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 |
|
||||
``` |
|
@ -1,5 +0,0 @@ |
|||||
{ |
|
||||
"forbid": [ |
|
||||
"/admin/*", "*.php" |
|
||||
] |
|
||||
} |
|
@ -1 +0,0 @@ |
|||||
<h1>Forbidden page</h1> |
|
@ -1 +0,0 @@ |
|||||
<h1>A permitted page</h1> |
|
@ -1,5 +0,0 @@ |
|||||
<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> |
|
@ -1 +0,0 @@ |
|||||
<?php echo "i'm coding PHP templatez!\n" ?>
|
|
@ -1,5 +0,0 @@ |
|||||
{ |
|
||||
"mime": { |
|
||||
"text/plain": [ "php" ] |
|
||||
} |
|
||||
} |
|
@ -1 +0,0 @@ |
|||||
<?php echo "i'm coding PHP templatez!\n" ?>
|
|
@ -1,8 +0,0 @@ |
|||||
{ |
|
||||
"mocks": [ |
|
||||
{ |
|
||||
"route": "/", |
|
||||
"module": "/mocks/delayed.js" |
|
||||
} |
|
||||
] |
|
||||
} |
|
@ -1,11 +0,0 @@ |
|||||
module.exports = { |
|
||||
name: 'delayed response', |
|
||||
response: function (ctx) { |
|
||||
return new Promise((resolve, reject) => { |
|
||||
setTimeout(() => { |
|
||||
ctx.body = '<h1>You waited 2s for this</h1>' |
|
||||
resolve() |
|
||||
}, 2000) |
|
||||
}) |
|
||||
} |
|
||||
} |
|
@ -1,58 +0,0 @@ |
|||||
{ |
|
||||
"mocks": [ |
|
||||
{ |
|
||||
"route": "/", |
|
||||
"response": { |
|
||||
"body": "<h1>Welcome to the Mock Responses example</h1>" |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
"route": "/one", |
|
||||
"response": { |
|
||||
"type": "text/plain", |
|
||||
"body": "<h1>Welcome to the Mock Responses example</h1>" |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
"route": "/two", |
|
||||
"request": { "accepts": "xml" }, |
|
||||
"response": { |
|
||||
"body": "<result id='2' name='whatever' />" |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
"route": "/three", |
|
||||
"responses": [ |
|
||||
{ |
|
||||
"request": { "method": "GET" }, |
|
||||
"response": { |
|
||||
"body": "<h1>Mock response for 'GET' request on /three</h1>" |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
"request": { "method": "POST" }, |
|
||||
"response": { |
|
||||
"status": 400, |
|
||||
"body": { "message": "That method is not allowed." } |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
}, |
|
||||
{ |
|
||||
"route": "/stream", |
|
||||
"module": "/mocks/stream-self.js" |
|
||||
}, |
|
||||
{ |
|
||||
"route": "/five/:id", |
|
||||
"module": "/mocks/five.js" |
|
||||
}, |
|
||||
{ |
|
||||
"route": "/users", |
|
||||
"module": "/mocks/users.js" |
|
||||
}, |
|
||||
{ |
|
||||
"route": "/users/:id", |
|
||||
"module": "/mocks/user.js" |
|
||||
} |
|
||||
] |
|
||||
} |
|
@ -1,6 +0,0 @@ |
|||||
module.exports = { |
|
||||
name: '/five/:id?name=:name', |
|
||||
response: function (ctx, id) { |
|
||||
ctx.body = `<h1>id: ${id}, name: ${ctx.query.name}</h1>` |
|
||||
} |
|
||||
} |
|
@ -1,8 +0,0 @@ |
|||||
const fs = require('fs') |
|
||||
|
|
||||
module.exports = { |
|
||||
name: 'stream response', |
|
||||
response: { |
|
||||
body: fs.createReadStream(__filename) |
|
||||
} |
|
||||
} |
|
@ -1,41 +0,0 @@ |
|||||
const users = require('./users.json') |
|
||||
|
|
||||
/* responses for /users/:id */ |
|
||||
const mockResponses = [ |
|
||||
/* don't support POST here */ |
|
||||
{ request: { method: 'POST' }, response: { status: 400 } }, |
|
||||
|
|
||||
/* for GET requests, return a particular user */ |
|
||||
{ |
|
||||
name: 'GET user', |
|
||||
request: { method: 'GET' }, |
|
||||
response: function (ctx, id) { |
|
||||
ctx.body = users.find(user => user.id === Number(id)) |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/* for PUT requests, update the record */ |
|
||||
{ |
|
||||
name: 'PUT user', |
|
||||
request: { method: 'PUT' }, |
|
||||
response: function (ctx, id) { |
|
||||
const updatedUser = ctx.request.body |
|
||||
const existingUserIndex = users.findIndex(user => user.id === Number(id)) |
|
||||
users.splice(existingUserIndex, 1, updatedUser) |
|
||||
ctx.status = 200 |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/* DELETE request: remove the record */ |
|
||||
{ |
|
||||
name: 'DELETE user', |
|
||||
request: { method: 'DELETE' }, |
|
||||
response: function (ctx, id) { |
|
||||
const existingUserIndex = users.findIndex(user => user.id === Number(id)) |
|
||||
users.splice(existingUserIndex, 1) |
|
||||
ctx.status = 200 |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
|
|
||||
module.exports = mockResponses |
|
@ -1,32 +0,0 @@ |
|||||
const users = require('./users.json') |
|
||||
|
|
||||
/* responses for /users */ |
|
||||
const mockResponses = [ |
|
||||
/* Respond with 400 Bad Request for PUT and DELETE - inappropriate on a collection */ |
|
||||
{ request: { method: 'PUT' }, response: { status: 400 } }, |
|
||||
{ request: { method: 'DELETE' }, response: { status: 400 } }, |
|
||||
{ |
|
||||
/* for GET requests return a subset of data, optionally filtered on 'minAge' and 'nationality' */ |
|
||||
request: { method: 'GET' }, |
|
||||
response: function (ctx) { |
|
||||
ctx.body = users.filter(user => { |
|
||||
const meetsMinAge = (user.age || 1000) >= (Number(ctx.query.minAge) || 0) |
|
||||
const requiredNationality = user.nationality === (ctx.query.nationality || user.nationality) |
|
||||
return meetsMinAge && requiredNationality |
|
||||
}) |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
/* for POST requests, create a new user and return the path to the new resource */ |
|
||||
request: { method: 'POST' }, |
|
||||
response: function (ctx) { |
|
||||
const newUser = ctx.request.body |
|
||||
users.push(newUser) |
|
||||
newUser.id = users.length |
|
||||
ctx.status = 201 |
|
||||
ctx.response.set('Location', `/users/${newUser.id}`) |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
|
|
||||
module.exports = mockResponses |
|
@ -1,5 +0,0 @@ |
|||||
[ |
|
||||
{ "id": 1, "name": "Lloyd", "age": 40, "nationality": "English" }, |
|
||||
{ "id": 2, "name": "Mona", "age": 34, "nationality": "Palestinian" }, |
|
||||
{ "id": 3, "name": "Francesco", "age": 24, "nationality": "Italian" } |
|
||||
] |
|
@ -1,37 +0,0 @@ |
|||||
const users = require('./users.json') |
|
||||
|
|
||||
/* responses for /users */ |
|
||||
const userResponses = [ |
|
||||
{ |
|
||||
route: '/users', |
|
||||
responses: [ |
|
||||
/* Respond with 400 Bad Request for PUT and DELETE - inappropriate on a collection */ |
|
||||
{ request: { method: 'PUT' }, response: { status: 400 } }, |
|
||||
{ request: { method: 'DELETE' }, response: { status: 400 } }, |
|
||||
{ |
|
||||
/* for GET requests return a subset of data, optionally filtered on 'minAge' and 'nationality' */ |
|
||||
request: { method: 'GET' }, |
|
||||
response: function (ctx) { |
|
||||
ctx.body = users.filter(user => { |
|
||||
const meetsMinAge = (user.age || 1000) >= (Number(ctx.query.minAge) || 0) |
|
||||
const requiredNationality = user.nationality === (ctx.query.nationality || user.nationality) |
|
||||
return meetsMinAge && requiredNationality |
|
||||
}) |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
/* for POST requests, create a new user and return the path to the new resource */ |
|
||||
request: { method: 'POST' }, |
|
||||
response: function (ctx) { |
|
||||
const newUser = ctx.request.body |
|
||||
users.push(newUser) |
|
||||
newUser.id = users.length |
|
||||
ctx.status = 201 |
|
||||
ctx.response.set('Location', `/users/${newUser.id}`) |
|
||||
} |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
] |
|
||||
|
|
||||
module.exports = userResponses |
|
@ -1,8 +0,0 @@ |
|||||
{ |
|
||||
"rewrite": [ |
|
||||
{ "from": "/css/*", "to": "/build/styles/$1" }, |
|
||||
{ "from": "/npm/*", "to": "http://registry.npmjs.org/$1" }, |
|
||||
{ "from": "/broken/*", "to": "http://localhost:9999" }, |
|
||||
{ "from": "/:user/repos/:name", "to": "https://api.github.com/repos/:user/:name" } |
|
||||
] |
|
||||
} |
|
@ -1,4 +0,0 @@ |
|||||
body { |
|
||||
font-family: monospace; |
|
||||
font-size: 1.3em; |
|
||||
} |
|
@ -1,24 +0,0 @@ |
|||||
<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": "/broken/*", "to": "http://localhost:9999" }, |
|
||||
{ "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="/broken/">/broken/</a></li> |
|
||||
<li><a href="/75lb/repos/work">/75lb/repos/work</a></li> |
|
||||
</ul> |
|
@ -1,8 +0,0 @@ |
|||||
module.exports = { |
|
||||
rewrite: [ |
|
||||
{ from: '/css/*', 'to': '/build/styles/$1' }, |
|
||||
{ from: '/npm/*', 'to': 'http://registry.npmjs.org/$1' }, |
|
||||
{ from: '/broken/*', 'to': 'http://localhost:9999' }, |
|
||||
{ from: '/:user/repos/:name', 'to': 'https://api.github.com/repos/:user/:name' } |
|
||||
] |
|
||||
} |
|
@ -1,7 +0,0 @@ |
|||||
body { |
|
||||
background-color: #AA3939; |
|
||||
color: #FFE2E2 |
|
||||
} |
|
||||
svg { |
|
||||
fill: #000 |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
<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> |
|
@ -1,7 +0,0 @@ |
|||||
{ |
|
||||
"name": "example", |
|
||||
"version": "1.0.0", |
|
||||
"local-web-server": { |
|
||||
"port": 8100 |
|
||||
} |
|
||||
} |
|
@ -1,4 +0,0 @@ |
|||||
{ |
|
||||
"spa": "index.html", |
|
||||
"no-cache": true |
|
||||
} |
|
@ -1,5 +0,0 @@ |
|||||
body { |
|
||||
background-color: IndianRed; |
|
||||
} |
|
||||
|
|
||||
a { color: black } |
|
Before Width: 91 | Height: 71 | Size: 2.3 KiB |
@ -1,14 +0,0 @@ |
|||||
<head> |
|
||||
<link rel="stylesheet" href="/css/style.css"> |
|
||||
</head> |
|
||||
<h1>Single Page App</h1> |
|
||||
<h2>Location: <span></span></h2> |
|
||||
<ul> |
|
||||
<li><a href="/login">/login</a></li> |
|
||||
<li><a href="/search">/search</a></li> |
|
||||
</ul> |
|
||||
<p>Found asset: <img src="image.jpg" /></p> |
|
||||
<p>Missing asset (should 404): <img src="sdafsadf.jpg" /></p> |
|
||||
<script> |
|
||||
document.querySelector('h2 span').textContent = window.location.pathname |
|
||||
</script> |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue