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 |
|||
node_js: |
|||
- 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