UNPKG

29.3 kBMarkdownView Raw
1# **RE**serve
2
3<table border="0" cellpadding="2" cellspacing="0">
4 <tr>
5 <td valign="top">
6 <strong>RE</strong>
7 </td>
8 <td>
9 <i>duced</i></br />
10 <i>levant</i></br />
11 <i>verse proxy</i><br />
12 <i>gexp-based</i><br />
13 <i>useable</i><br />
14 <strong>serve</strong>
15 </td>
16 </tr>
17</table>
18
19[![Travis-CI](https://travis-ci.org/ArnaudBuchholz/reserve.svg?branch=master)](https://travis-ci.org/ArnaudBuchholz/reserve#)
20[![Coverage Status](https://coveralls.io/repos/github/ArnaudBuchholz/reserve/badge.svg?branch=master)](https://coveralls.io/github/ArnaudBuchholz/reserve?branch=master)
21[![Maintainability](https://api.codeclimate.com/v1/badges/49e3adbc8f31ae2febf3/maintainability)](https://codeclimate.com/github/ArnaudBuchholz/reserve/maintainability)
22[![Package Quality](https://npm.packagequality.com/shield/reserve.svg)](https://packagequality.com/#?package=reserve)
23[![Known Vulnerabilities](https://snyk.io/test/github/ArnaudBuchholz/reserve/badge.svg?targetFile=package.json)](https://snyk.io/test/github/ArnaudBuchholz/reserve?targetFile=package.json)
24[![dependencies Status](https://david-dm.org/ArnaudBuchholz/reserve/status.svg)](https://david-dm.org/ArnaudBuchholz/reserve)
25[![devDependencies Status](https://david-dm.org/ArnaudBuchholz/reserve/dev-status.svg)](https://david-dm.org/ArnaudBuchholz/reserve?type=dev)
26[![reserve](https://badge.fury.io/js/reserve.svg)](https://www.npmjs.org/package/reserve)
27[![reserve](http://img.shields.io/npm/dm/reserve.svg)](https://www.npmjs.org/package/reserve)
28[![install size](https://packagephobia.now.sh/badge?p=reserve)](https://packagephobia.now.sh/result?p=reserve)
29[![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
30[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FArnaudBuchholz%2Freserve.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FArnaudBuchholz%2Freserve?ref=badge_shield)
31
32A **lightweight** web server statically **configurable** with regular expressions.
33It can also be **embedded** and **extended**.
34
35# Rational
36
37Initially started to build a local **development environment** where static files are served and resources can be fetched from remote repositories, this **tool** is **versatile** and can support different scenarios :
38- A simple web server
39- A reverse proxy to an existing server
40- A server that aggregates several sources
41- ...
42
43By defining **an array of mappings**, one can decide how the server will process the requests. Each mapping associates a **matching** criterion defined with a
44[regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) to a **handler** that will answer the request.
45
46The configuration syntax favors **simplicity** without dropping flexibility.
47
48For instance, the definition of a server that **exposes files** of the current directory but **forbids access** to the directory `private` consists in :
49
50```json
51{
52 "port": 8080,
53 "mappings": [{
54 "match": "^/private/.*",
55 "status": 403
56 }, {
57 "match": "^/(.*)",
58 "file": "./$1"
59 }]
60}
61```
62
63## More documentation
64
65Go to this [page](https://github.com/ArnaudBuchholz/reserve/tree/master/doc/README.md) to access articles about REserve.
66
67# Usage
68
69## In a project
70
71* Install the package with `npm install reserve` *(you decide if you want to save it as development dependency or not)*
72* You may create a start script in `package.json` :
73
74```json
75{
76 "scripts": {
77 "start": "reserve"
78 }
79}
80```
81
82* By default, it will look for a file named `reserve.json` in the current working directory
83* A configuration file name can be specified using `--config <file name>`, for instance :
84
85```json
86{
87 "scripts": {
88 "start": "reserve",
89 "start-dev": "reserve --config reserve-dev.json"
90 }
91}
92```
93
94## Global
95
96* Install the package with `npm install reserve --global`
97* Run `reserve`
98 * By default, it will look for a file named `reserve.json` in the current working directory
99 * A configuration file name can be specified using `--config <file name>`
100
101**NOTE** : if [`process.send`](https://nodejs.org/api/process.html#process_process_send_message_sendhandle_options_callback) is defined, REserve will notify the parent process when the server is ready by sending the message `'ready'`.
102
103# Embedding
104
105It is possible to implement the server in any application using the `reserve/serve` module :
106
107```javascript
108const path = require('path')
109const reserve = require('reserve/serve')
110reserve({
111 port: 8080,
112 mappings: [{
113 match: /^\/(.*)/,
114 file: path.join(__dirname, '$1')
115 }]
116})
117 .on('ready', ({ url }) => {
118 console.log(`Server running at ${url}`)
119 })
120```
121
122The resulting object implements the [EventEmitter](https://nodejs.org/api/events.html) class and throws the following events with parameters :
123
124| Event | Parameter *(object containing members)* | Description |
125|---|---|---|
126| **server-created** | `server` *([`http.server`](https://nodejs.org/api/http.html#http_class_http_server) or [`https.server`](https://nodejs.org/api/https.html#https_class_https_server))*, `configuration` *([configuration interface](#configuration-interface))*| Only available to `listeners`, this event is triggered after the HTTP(S) server is **created** and **before it accepts requests**.
127| **ready** | `url` *(String, example : `'http://0.0.0.0:8080/'`)*| The server is listening and ready to receive requests, hostname is replaced with `0.0.0.0` when **unspecified**.
128| **incoming** | `method` *(String, example : `'GET'`)*, `url` *(String)*, `start` *(Date)* | New request received, these parameters are also transmitted to **error**, **redirecting** and **redirected** events |
129| **error** | `reason` *(Any)* | Error reason, contains **incoming** parameters if related to a request |
130| **redirecting** | `type` *(Handler type, example : `'status'`)*, `redirect` *(String or Number, example : `404`)* | Processing redirection to handler, gives handler type and redirection value. <br />*For instance, when a request will be served by the [file handler](#file), this event is generated once. But if the requested resource does not exist, the request will be redirected to the [status](#status) 404 triggering again this event.* |
131| **redirected** | `end` *(Date)*, `timeSpent` *(Number of ms)*, `statusCode` *(Number)* | Request is fully processed. `timeSpent` is evaluated by comparing `start` and `end` (i.e. not using high resolution timers) and provided for information only. |
132
133The package also gives access to the configuration reader :
134
135```javascript
136const path = require('path')
137const { read } = require('reserve/configuration')
138const reserve = require('reserve/serve')
139read('reserve.json')
140 .then(configuration => {
141 reserve(configuration)
142 .on('ready', ({ url }) => {
143 console.log(`Server running at ${url}`)
144 })
145 })
146```
147
148And a default log output *(verbose mode will dump all redirections)* :
149
150```javascript
151const path = require('path')
152const { read } = require('reserve/configuration')
153const log = require('reserve/log')
154const reserve = require('reserve/serve')
155read('reserve.json')
156 .then(configuration => {
157 log(reserve(configuration), /*verbose: */ true)
158 })
159```
160
161NOTE: log is using [`colors`](https://www.npmjs.com/package/colors) **if installed**.
162
163# Configuration
164
165## hostname *(optional)*
166
167Used to set the `host` parameter when calling http(s) server's [listen](https://nodejs.org/api/net.html#net_server_listen).
168
169Default is `undefined`.
170
171## port *(optional)*
172
173Used to set the `port` parameter when calling http(s) server's [listen](https://nodejs.org/api/net.html#net_server_listen).
174
175Default is `5000`.
176
177## max-redirect *(optional)*
178
179Limits the number of internal redirections. If the number of redirections goes beyond the parameter value, the request fails with error [`508`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508).
180
181Default is `10`.
182
183## ssl *(optional)*
184
185This object provides certificate information to build an https server. You might be interested by the article [An Express HTTPS server with a self-signed certificate](https://flaviocopes.com/express-https-self-signed-certificate/).
186
187The object must contain :
188* `cert` : a relative or absolute path to the certificate file
189* `key` : a relative or absolute path to the key file
190
191If relative, the configuration file directory or the current working directory (when embedding) is considered.
192
193## handlers
194
195An object associating a handler prefix to a handler object.
196If the property value is a string, the handler is obtained using [require](https://nodejs.org/api/modules.html#modules_require_id).
197
198For instance : every mapping containing the `cache` property will be associated to the [REserve/cache](https://www.npmjs.com/package/reserve-cache) handler.
199
200```json
201{
202 "handlers": {
203 "cache": "reserve-cache"
204 }
205}
206```
207
208**NOTE** : it is not possible to change the associations of the default prefixes (`custom`, `file`, `status`, `url`, `use`). **No error** will be thrown if a prefix collides with a predefined one.
209
210See [Custom handlers](#custom-handlers) for more information.
211
212## mappings
213
214An array of mappings that is evaluated in the order of declaration.
215* Several mappings may apply to the same request
216* Evaluation stops when the request is **finalized** *(see the note below)*
217* When a handler triggers a redirection, the array of mappings is re-evaluated
218
219**NOTE** : REserve hooks the [`response.end`](https://nodejs.org/api/http.html#http_response_end_data_encoding_callback) API to detect when the response is finalized.
220
221Each mapping must contain :
222* `match` *(optional)* : a string (converted to a regular expression) or a regular expression that will be applied to the [request URL](https://nodejs.org/api/http.html#http_message_url), defaulted to `"(.*)"`
223* `method` *(optional)* : a comma separated string or an array of [HTTP verbs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) that is matched with the [request method](https://nodejs.org/api/http.html#http_message_method), defaulted to `undefined` *(meaning all methods are allowed)*.
224* the handler prefix (`custom`, `file`, `status`, `url`, `use` ...) which value may contain capturing groups *(see [Custom handlers](#custom-handlers))*
225* `cwd` *(optional)* : the current working directory to consider for relative path, defaulted to the configuration file directory or the current working directory (when embedding)
226
227**NOTE** : when using `custom` in a [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) file, since functions can't be used in this format, the expected value is a string referencing the relative or absolute module to load. If relative, the `cwd` member is considered.
228
229**NOTE** : each **handler may provide its own `method` parameter** depending on which verbs are implemented. The mapping's `method` value **cannot** allow a verb that is not implemented. As a consequence **an error is thrown** if the combination of handler and mapping `method` parameters leads to an empty list.
230
231For instance :
232
233* `reserve.json` :
234
235```json
236{
237 "port": 8080,
238 "mappings": [{
239 "custom": "./cors"
240 }, {
241 "match": "^/(.*)",
242 "file": "./$1"
243 }]
244}
245```
246
247* `cors.js` :
248
249```javascript
250module.exports = async (request, response) => response.setHeader('Access-Control-Allow-Origin', '*')
251```
252
253## listeners
254
255An array of **functions** or **module names exporting a function** which will be called with the **REserve [EventEmitter](https://nodejs.org/api/events.html) object**. The purpose is to allow events registration before the server starts and give access to the `server-created` event.
256
257## extend
258
259*Only for JSON configuration*
260
261A relative or absolute path to another configuration file to extend.
262If relative, the current configuration file directory is considered.
263
264The current settings overwrite the ones coming from the extended configuration file.
265
266Extended `mappings` are imported at the end of the resulting array, making the current ones being evaluated first. This way, it is possible to override the extended mappings.
267
268# Handlers
269
270## file
271
272Answers the request using **file system**.
273
274Example :
275```json
276{
277 "match": "^/(.*)",
278 "file": "./$1"
279}
280```
281
282* Only supports [GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET)
283* Capturing groups can be used as substitution parameters
284* Absolute or relative to the handler's `cwd` member *(see [mappings](#mappings))*
285* Incoming URL parameters are automatically stripped out to simplify the matching expression
286* Directory access is internally redirected to the inner `index.html` file *(if any)* or `404` status
287* File access returns `404` status if missing or can't be read
288* Mime type computation is based on [`mime`](https://www.npmjs.com/package/mime) **if installed**. Otherwise a limited subset of mime types is used:
289
290|Extension|mime type|
291|---|---|
292|bin|application/octet-stream|
293|css|text/css|
294|gif|image/gif|
295|html|text/html|
296|htm|text/html|
297|jpeg|image/jpeg|
298|jpg|image/jpeg|
299|js|application/javascript|
300|pdf|application/pdf|
301|png|image/png|
302|svg|image/svg+xml|
303|text|text/plain|
304|txt|text/plain|
305|xml|application/xml|
306
307| option | type | default | description |
308|---|---|---|---|
309| `case-sensitive` | Boolean | `false` | *(for Windows)* when `true`, the file path is tested case sensitively. Since it has an impact on **performances**, use carefully. |
310| `ignore-if-not-found` | Boolean | `false` | If the mapping does not resolve to a file or a folder, the handler does not end the request with status `404`. |
311
312## url
313
314Answers the request by **forwarding** it to a different URL.
315
316Example :
317```json
318{
319 "match": "^/proxy/(https?)/(.*)",
320 "url": "$1://$2",
321 "unsecure-cookies": true
322}
323```
324
325* Supports [all HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
326* Capturing groups can be used as substitution parameters
327* Redirects to any URL (http or https)
328
329**NOTE** : It must redirect to an absolute URL
330
331| option | type | default | description |
332|---|---|---|---|
333| `unsecure-cookies` | Boolean | `false` | when `true`, the secured cookies are converted to unsecure ones. Hence, the browser will keep them even if not running on https |
334| `forward-request` | String or Function | - | when specified, the function is called **before** generating the forward request. The expected signature is `function ({ configuration, context, mapping, match, request: { method, url, headers }})`. Changing the request settings will **impact** the forward request.
335| `forward-response` | String or Function | - | when specified, the function is called **after** sending the forward request but **before** writing the current request's response. The expected signature is `function ({ configuration, context, mapping, match, headers })`. Changing the headers will directly impact the current request's response.
336
337**NOTE** : When a string is used for `forward-request` or `forward-response`, the corresponding function is loaded with [require](https://nodejs.org/api/modules.html#modules_require_id).
338
339**NOTE** : The `context` parameter is a unique object *(one per request)* allocated to link the `forward-request` and `forward-response` callbacks. It enables **request-centric communication** between the two: whatever members you add on it during the `forward-request` callback will be kept and transmitted to the `forward-response` callback.
340
341## custom
342
343Enables 'simple' **custom** handlers.
344
345Examples :
346```javascript
347{
348 custom: async (request, response) => response.setHeader('Access-Control-Allow-Origin', '*')
349}
350```
351
352Or using an external module :
353
354```javascript
355{
356 custom: './cors.js'
357}
358```
359
360with `cors.js` :
361```javascript
362module.exports = async (request, response) => response.setHeader('Access-Control-Allow-Origin', '*')
363```
364
365External modules are loaded with Node.js [require](https://nodejs.org/api/modules.html#modules_require_id) API.
366
367`custom` must point to a [function](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Functions)
368* That takes at least two parameters : [`request`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) and [`response`](https://nodejs.org/api/http.html#http_class_http_serverresponse)
369* Capturing groups' values are passed as additional parameters.
370* This function must return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
371* If the promise is resolved to a value (i.e. not `undefined`), an internal redirection occurs i.e. the request is going over the mappings again (*infinite loops are now prevented, see `max-redirect`*).
372* If the `response` is not **finalized** after executing the function *(i.e. [`response.end`](https://nodejs.org/api/http.html#http_response_end_data_encoding_callback) was not called)*, the `request` is going over the remaining mappings
373
374| option | type | default | description |
375|---|---|---|---|
376| `watch` | Boolean | `false` | when `true` and using a local module *(does not work with `node_modules`)* the file's modified time is checked before executing the handler. When changed, the module is reloaded |
377
378## status
379
380**Ends** the request with a given status.
381
382Example :
383```json
384{
385 "match": "^/private/.*",
386 "status": 403
387}
388```
389
390* Supports [all HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
391* Accepts only Numbers
392* Also used when an internal redirection to a Number occurs
393* Capturing groups can be used in the headers' values
394* End the response with the given status and, if defined, with a textual message :
395
396| status | message |
397|---|---|
398| 403 | Forbidden |
399| 404 | Not found |
400| 405 | Method Not Allowed |
401| 500 | Internal Server Error |
402| 501 | Not Implemented |
403| 508 | Loop Detected |
404
405| option | type | default | description |
406|---|---|---|---|
407| `headers` | Object | `{}` | Additional response headers (capturing groups can be used as substitution parameters in values) |
408
409## use
410
411Enables the use of [express middleware functions](https://www.npmjs.com/search?q=keywords%3Aexpress%20keywords%3Amiddleware).
412
413**NOTE** : Supports only middleware functions accepting exactly three parameters (`request`, `response` and `next`) as described [here](http://expressjs.com/en/guide/writing-middleware.html).
414
415**NOTE** : This is an **experimental feature** that needs deeper testing.
416
417Example :
418
419```json
420{
421 "use": "express-session",
422 "options" : {
423 "secret": "keyboard cat",
424 "resave": false,
425 "saveUninitialized": true
426 }
427}
428```
429
430| option | type | default | description |
431|---|---|---|---|
432| `options` | Object | `{}` | Options passed to the middleware factory |
433
434## Other handlers
435
436The following handlers can be installed separately and plugged through the `handlers` configuration property.
437
438| handler | description |
439|---|---|
440| [REserve/cache](https://www.npmjs.com/package/reserve-cache) | Caches string in memory |
441| [REserve/cmd](https://www.npmjs.com/package/reserve-cmd) | Wraps command line execution |
442| [REserve/fs](https://www.npmjs.com/package/reserve-fs) | Provides [fs](https://nodejs.org/api/fs.html) APIs to the browser |
443
444## Custom handlers
445
446A custom handler object may define:
447
448* **schema** *(optional)* a mapping validation schema, see [below](#schema) for the proposed syntax
449
450* **method** *(optional)* a comma separated string or an array of [HTTP verbs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) that indicates which methods are implemented. When no value is provided, REserve considers that any verb is supported.
451
452* **validate** *(optional)* an [asynchronous](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) method that validates mapping definition, it will be called with two **parameters**:
453 - **mapping** the mapping being validated
454 - **configuration** the [configuration interface](#configuration-interface)
455
456
457* **redirect** *(mandatory)* an [asynchronous](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) method that will be called with an **object** exposing:
458 - **configuration** the [configuration interface](#configuration-interface)
459 - **mapping** the mapping being executed
460 - **match** the regular expression [exec result](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec)
461 - **redirect** the value associated with the handler prefix in the mapping. Capturing groups **are** substituted.
462 - **request** Node.js' [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
463 - **response** Node.js' [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse)
464
465### Capturing groups and interpolation
466
467By default, the handler prefix is **interpolated**. Identified **placeholders are substituted** with values coming from the capturing groups of the matching regular expression.
468
469Three syntaxes are accepted for placeholders, `<index>` represents the capturing group index in the regular expression (1-based):
470* `$<index>` value is replaced **as-is**
471* `$&<index>` value is first **decoded** with [decodeURI](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI)
472* `$%<index>` value is first **decoded** with [decodeURIComponent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent)
473
474When writing an handler, it is possible to **reuse the mechanism** by importing the function `require('reserve').interpolate`. It accepts two parameters:
475* **match** the regular expression [exec result](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec)
476* **value** accepting multiple types :
477 - `string` : value is interpolated and the result returned
478 - `object` : property values are interpolated **recursively** and a new object is returned
479 - otherwise the value is returned **as-is**
480
481### Schema
482
483The schema syntax is designed to be short and self-explanatory. It is a dictionary mapping a property name to its specification.
484
485It can be either:
486* Simple type specification (for instance: `"string"`)
487* Multiple types specification (for instance `["function", "string"]`)
488* Complete specification: an object containing `type` (or `types`) and a `defaultValue` for optional properties.
489
490For instance, the following schema specification defines:
491* a **mandatory** `custom` property that must be either a `function` or a `string`
492* an **optional** `watch` boolean property which default value is `false`
493
494```json
495{
496 "schema": {
497 "custom": ["function", "string"],
498 "watch": {
499 "type": "boolean",
500 "defaultValue": false
501 }
502 }
503}
504```
505
506If provided, the schema is applied on the mapping **before** the **validate** function.
507
508### Configuration interface
509
510The configuration interface lets you access the dictionary of handlers (member `handlers`) as well as the array of existing mappings (member `mappings`).
511
512It is recommended to be extremely careful when manipulating the mappings' content, since you might break the logic of the server.
513
514It is possible to safely change the list of mapping using the [asynchronous](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) `setMappings` method. This API takes two parameters: the new list of mappings and the current request.
515
516The API will:
517* validate any new mapping *(relies on an internal detection mechanism based on symbols)*
518* wait for all pending requests to be completed before applying the new list
519
520# Helpers
521
522## body
523
524Since version 1.4.0, the package offers a **basic** method to **read the request body**.
525
526```javascript
527const { body } = require('reserve')
528
529async function customHandler (request, response) {
530 const requestBody = JSON.parse(await body(request))
531 /* ... */
532}
533```
534
535## capture
536
537Since version 1.8.0, the package offers a mechanism to **capture the response stream** and **duplicate its content** to another **writable stream**.
538
539**NOTE** : The content is decoded if the [`content-encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) header contains: `gzip`, `deflate` or `br` *(only one, no combination is supported)*.
540
541**NOTE** : Check the [version of Node.js](https://nodejs.org/api/zlib.html#zlib_class_zlib_brotlicompress) to enable `br` compression support.
542
543For instance, it enables the caching of downloaded resources :
544
545```JavaScript
546mappings: [{
547 match: /^\/(.*)/,
548 file: './cache/$1',
549 'ignore-if-not-found': true
550}, {
551 method: 'GET',
552 custom: async (request, response) => {
553 if (/\.(js|css|svg|jpg)$/.exec(request.url)) {
554 const cachePath = join(cacheBasePath, '.' + request.url)
555 const cacheFolder = dirname(cachePath)
556 await mkdirAsync(cacheFolder, { recursive: true })
557 const file = createWriteStream(cachePath) // auto closed
558 capture(response, file)
559 .catch(reason => {
560 console.error(`Unable to cache ${cachePath}`, reason)
561 })
562 }
563 }
564}, {
565 match: /^\/(.*)/,
566 url: 'http://your.website.domain/$1'
567}]
568```
569
570# Mocking
571
572Since version 1.1.0, the package includes the helper `reserve/mock` to build tests. This method receives a configuration (like `reserve/serve`) and returns a promise resolving to an [EventEmitter](https://nodejs.org/api/events.html) augmented with a `request` method :
573
574```javascript
575function request (method, url, headers = {}, body = '') {
576 return Promise.resolve(mockedResponse)
577}
578```
579
580Call the `request` method to simulate an incoming request, it returns a promise resolving to a mocked response exposing the following members :
581
582| Member | Type | Description |
583|---|---|---|
584| **headers** | Object | Response headers
585| **statusCode** | Number | Status code
586| **finished** | Boolean | `true`
587| **toString()** | String | Gives the response body
588
589Example :
590
591```javascript
592require('reserve/mock')({
593 port: 8080,
594 mappings: [{
595 match: /^\/(.*)/,
596 file: path.join(__dirname, '$1')
597 }]
598})
599 .then(mocked => mocked.request('GET', '/'))
600 .then(response => {
601 assert(response.statusCode === 200)
602 assert(response.toString() === '<html />')
603 })
604```
605
606You may provide mocked handlers *(based on their [actual implementation](https://github.com/ArnaudBuchholz/reserve/tree/master/handlers))*:
607
608```javascript
609require('reserve/mock')({
610 port: 8080,
611 mappings: [{
612 match: /^\/(.*)/,
613 file: path.join(__dirname, '$1')
614 }]
615}, {
616 file: {
617 redirect: async ({ request, mapping, redirect, response }) => {
618 if (redirect === '/') {
619 response.writeHead(201, {
620 'Content-Type': 'text/plain',
621 'Content-Length': 6
622 })
623 response.end('MOCKED')
624 } else {
625 return 500
626 }
627 }
628 }
629})
630 .then(mocked => mocked.request('GET', '/'))
631 .then(response => {
632 assert(response.statusCode === 201)
633 assert(response.toString() === 'MOCKED')
634 })
635```
636
637# Version history
638
639|Version|content|
640|---|---|
641|1.0.0|Initial version|
642|1.0.5|`watch` option in **custom** handler|
643|1.1.1|[`require('reserve/mock')`](#mocking)|
644||[`colors`](https://www.npmjs.com/package/colors) and [`mime`](https://www.npmjs.com/package/mime) are no more dependencies|
645|1.1.2|Performance testing, `--silent`|
646||`case-sensitive` option in **file** handler|
647|1.1.3|Changes default hostname to `undefined`|
648|1.1.4|Enables external handlers in `json` configuration through [require](https://nodejs.org/api/modules.html#modules_require_id)|
649|1.1.5|Fixes relative path use of external handlers in `json` configuration|
650|1.1.6|Improves response mocking (`flushHeaders()` & `headersSent`)|
651|1.1.7|Compatibility with Node.js >= 12.9|
652||Improves response mocking|
653|1.2.0|Implements handlers' schema|
654||Gives handlers access to a configuration interface|
655||Prevents infinite loops during internal redirection (see `max-redirect`)|
656|1.2.1|Fixes coloring in command line usage|
657|1.3.0|Fixes infinite loop in the error handler|
658||Adds *experimental* `use` handler for [express middleware functions](https://www.npmjs.com/search?q=keywords%3Aexpress%20keywords%3Amiddleware)|
659||Makes the mapping `match` member optional|
660|1.4.0|More [documentation](https://github.com/ArnaudBuchholz/reserve/tree/master/doc/README.md) |
661||Exposes simple body reader (`require('reserve').body`)|
662||Adds `method` specification *(handlers & mappings)*|
663|1.5.0|`headers` option in **status** handler *(enables HTTP redirect)*|
664||`ignore-if-not-found` option in **file** handler *(enables folder browsing with a separate handler)*|
665|1.6.0|Implements `$%1` and `$&1` substitution parameters *(see [Custom handlers](#custom-handlers))*|
666|1.6.1|Exposes `require('reserve').interpolate` *(see [Custom handlers](#custom-handlers))*|
667|1.7.0|Adds `listeners` configuration option|
668||Adds `server-created` event available only to listeners|
669||Secures events processing against exceptions|
670||Adds `forward-request` and `forward-response` options for the `url` handler|
671|1.7.1|Adds more context to `forward-request` and `forward-response` callbacks|
672|1.8.0|Improves end of streaming detection in `file` and `url` handlers|
673||`capture` helper *(experimental)*|
674||`custom` handler validation *(improved)*|