Based on a clean concept, the development of REserve follows a simple architecture that enables flexibility and extensibility.
This article provides keys to understand the modular structure of the implementation.
By defining an array of mappings, one can decide how the server will process the incoming requests. Each mapping associates a matching criterion defined with a regular expression to a handler that will answer the request.
When a mapping is defined, its association to the handler is made through a specific property (called prefix; for instance : file
).
The value of this property configures how the handler must behave when executed. For instance, the file handler expects a file path.
Capturing groups can be defined in the matching regular expression to extract parts of the URL and reuse them in the property (they are identified with $1
, $2
… ).
Consequently, the following mapping will match the URL /sample/index.html
and serve the request with the file ./www/sample/index.html
.
{
"match": "^/(.*)",
"file": "./www/$1"
}
REserve is designed on a simple architecture summarized by the following Block diagram
TAM Block diagram of the technical architecture
There are 4 main building blocks (a.k.a. agents) namely :
These are required to run the server but one last additional component enables testing : the mock one.
The configuration agent exposes two methods.
On one hand, it offers the read
method capable of reading JSON configuration files. To enforce reusability, it also supports the inclusion of other files using the extends
keyword. When deserialized this way, every path is relative to the folder where the configuration file is stored.
const { read } = require('reserve')
read('reserve.json').then(configuration => /* ... */)
Example of
read
On the other hand, either after reading the configuration from a file or once it was built from a literal, the agent exposes the check
method to validate the configuration.
In particular :
validate
methodconst { check } = require('reserve')
check({
port: 8080,
mappings: [{
/* ... */
}]
}).then(configuration => /* ... */)
Example of
check
The serve agent is responsible of :
Provided you have a verified configuration object, it is the main entry point to start the server.
const { serve } = require('reserve')
serve({ /* configuration */ })
.on('ready', ({ url }) => {
console.log(`Server running at ${url}`)
})
Example of
serve
The dispatcher agent is the heart of REserve; it routes the received requests to the different handlers using the following algorithm :
request.url
with mappings’ regular expressionsredirect
methodresponse.end
is calledThis cycle is illustrated in the following activity diagram.
TAM Activity diagram of the dispatcher algorithm
Each handler implements a redirect
method receiving :
Five handlers are provided out of the box with REserve.
file
The file
handler answers requests by serving files from the local file system.
It supports only the verb GET
and a limited list of mime types.
It also implements helpful behaviors :
index.html
file (if any).For instance, this mapping will answer the URL /sample/index.html?parameter=value#hash
with the content of the file ./www/sample/index.html
(if it exists).
{
"match": "^/(.*)",
"file": "./www/$1"
}
Example of a
file
mapping
The url
handler forwards the incoming requests to a remote URL, all verbs are supported.
For instance, this mapping will answer the URL /proxy/https/npmjs.com/package/reserve
with the content of URL https://www.npmjs.com/package/reserve
.
{
"match": "^/proxy/(https?)/(.*)",
"url": "$1://$2"
}
Example of a
url
mapping
The custom
handler offers a simplified interface to create custom endpoints.
The code might come from an external module or by passing a function
.
For instance, this mapping will add the response header 'Access-Control-Allow-Origin'
to all incoming requests and the processing will go through the remaining mappings.
{
"custom": "./cors.js"
}
Example of
custom
mapping
The custom function is implemented in a separate file.
module.exports = (request, response) => response.setHeader('Access-Control-Allow-Origin', '*')
cors.js
The status
handler ends any request with a given status.
The use
handler enables the reuse of express middleware functions. It can be seen as an adapter to fit express middleware functions in REserve.
For instance, this mapping integrates the express-session middleware to create sessions.
{
"use": "express-session",
"options" : {
"secret": "keyboard cat",
"resave": false,
"saveUninitialized": true
}
}
Example of
use
mapping