From e2a2b2b98ba7e2b19cd065995ddbfd7669435d07 Mon Sep 17 00:00:00 2001 From: Andi Dittrich Date: Sun, 15 Nov 2020 11:55:57 +0100 Subject: [PATCH] refactored codebase --- CHANGES.md | 17 ++++++ README.md | 75 +++++++++++++++++------ assets/template.ejs | 14 ++--- bin/generator.js | 140 +++++++++++++++++++------------------------ config-dist.json | 9 +-- config.json | 14 ++--- dist/HTTP400.html | 4 +- dist/HTTP401.html | 4 +- dist/HTTP403.html | 4 +- dist/HTTP404.html | 4 +- dist/HTTP500.html | 4 +- dist/HTTP501.html | 4 +- dist/HTTP502.html | 4 +- dist/HTTP503.html | 4 +- dist/HTTP520.html | 4 +- dist/HTTP521.html | 4 +- dist/HTTP533.html | 4 +- docs/HTTP400.html | 6 +- docs/HTTP401.html | 6 +- docs/HTTP403.html | 6 +- docs/HTTP404.html | 6 +- docs/HTTP500.html | 6 +- docs/HTTP501.html | 6 +- docs/HTTP502.html | 6 +- docs/HTTP503.html | 6 +- docs/HTTP520.html | 6 +- docs/HTTP521.html | 6 +- docs/HTTP533.html | 6 +- docs/config.json | 2 +- examples/express.js | 24 +++++++- examples/koa.js | 17 +++++- lib/dispatcher.js | 36 +++++++++++ lib/express.js | 72 +++++----------------- lib/json-reader.js | 15 ++--- lib/koa.js | 72 +++++----------------- lib/options.js | 17 ++++++ lib/page-renderer.js | 22 +++++-- lib/resources.js | 22 +++++++ package.json | 10 ++-- 39 files changed, 370 insertions(+), 318 deletions(-) create mode 100644 lib/dispatcher.js create mode 100644 lib/options.js create mode 100644 lib/resources.js diff --git a/CHANGES.md b/CHANGES.md index 378c231..0c82dbf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,23 @@ CHANGELOG ====================================================== +Branch 3.X.X +------------------------------------------------------ + +### 3.0.0 ### + +* Refactored the whole codebase +* Added: template support for `pagetitle` (automatically set if not defined) +* Added: option to set the pagetitle directly via `payload.pagetitle` (koa+expressjs) +* Added: filter function to dynamically manipulate the errordata object (koa+expressjs) +* Added: support for additional variables/payloads +* Added: support for placeholders to static page generator +* Added: iso-639-1 `language` attribute (derived from page lang) +* Added: error/exception object available via `error` (koa+expressjs) +* Added: `onError` callback as debug error handler (not to be used in production) +* Changed: moved `footer` content into `payload` object +* Changed: all variables within the `ejs` template are only accessible via `vars` object + Branch 2.X.X ------------------------------------------------------ diff --git a/README.md b/README.md index 8be225f..14be2b6 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,10 @@ async function bootstrap(){ // because of the asynchronous file-loaders, wait until it has been executed await _httpErrorPages.express(_webapp, { lang: 'en_US', - footer: 'Hello World' + payload: { + footer: 'Hello World', + myvar: 'hello world' + } }); // start service @@ -174,10 +177,13 @@ bootstrap() Syntax: `Promise _httpErrorPages.express(expressWebapp [, options:Object])` -* `template` - the path to a custom **EJS** template used to generate the pages. default [assets/template.ejs](assets/template.ejs) -* `css` - the path to a precompiled **CSS** file injected into the page. default [assets/layout.css](assets/layout.css) -* `footer` - optional page footer content (html allowed). default **null** -* `lang` - language definition which should be used (available in the `i18n/` directory). default **en_US** +* `template` (type:string) - the path to a custom **EJS** template used to generate the pages. default [assets/template.ejs](assets/template.ejs) +* `css` (type:string) - the path to a precompiled **CSS** file injected into the page. default [assets/layout.css](assets/layout.css) +* `lang` (type:string) - language definition which should be used (available in the `i18n/` directory). default **en_US** +* `payload` (type:object) - additional variables available within the template +* `payload.footer` (type:string) - optional page footer content (html allowed). default **null** +* `filter` (type:function) - filter callback to manipulate the variables before populated within the template +* `onError` (type:function) - simple debug handler to print errors to the console (not to be used in production!) ## koajs Integration ## @@ -203,7 +209,11 @@ const _httpErrorPages = require('http-error-pages'); // because of the asynchronous file-loaders, wait until it has been executed - it returns an async handler _webapp.use(await _httpErrorPages.koa({ lang: 'en_US', - footer: 'Hello World' + payload: { + footer: 'Hello World', + myvar: 'hello world' + } + })); // add other middleware handlers @@ -224,10 +234,13 @@ _webapp.listen(8888); Syntax: `Promise _httpErrorPages.koa([options:Object])` -* `template` - the path to a custom **EJS** template used to generate the pages. default [assets/template.ejs](assets/template.ejs) -* `css` - the path to a precompiled **CSS** file injected into the page. default [assets/layout.css](assets/layout.css) -* `footer` - optional page footer content (html allowed). default **null** -* `lang` - language definition which should be used (available in the `i18n/` directory). default **en_US** +* `template` (type:string) - the path to a custom **EJS** template used to generate the pages. default [assets/template.ejs](assets/template.ejs) +* `css` (type:string) - the path to a precompiled **CSS** file injected into the page. default [assets/layout.css](assets/layout.css) +* `lang` (type:string) - language definition which should be used (available in the `i18n/` directory). default **en_US** +* `payload` (type:object) - additional variables available within the template +* `payload.footer` (type:string) - optional page footer content (html allowed). default **null** +* `filter` (type:function) - filter callback to manipulate the variables before populated within the template +* `onError` (type:function) - simple debug handler to print errors to the console (not to be used in production!) ## Caddy Integration ## @@ -390,7 +403,7 @@ The footer message can easily be changed/removed by editing [config.json](config ```js { // Output Filename Scheme - eg. HTTP500.html - "scheme": "HTTP%d.html", + "scheme": "HTTP%code%.html", // Footer content (HTML Allowed) "footer": "Contact info@example.org" @@ -402,23 +415,49 @@ The footer message can easily be changed/removed by editing [config.json](config ```js { // Output Filename Scheme - eg. HTTP500.html - "scheme": "HTTP%d.html" + "scheme": "HTTP%code%.html" } ``` +### Placeholders/Variables ### + +The following set of variables is exposed to the ejs template (404 page example): + +```js +{ + title: 'Resource not found', + message: 'The requested resource could not be found but may be available again in the future.', + code: '404', + language: 'en', + scheme: 'HTTP%code%.html', + pagetitle: "We've got some trouble | %code% - %title%", + footer: 'Tech Contact info@example.org', + myvar: 'Hello World' +} +``` + +To generate dynamic titles/content based on the current variable set, each variable is exposed as `placeholder` (surrounded by `%`). + +You can also define custom variable within the page definitions, everything is merged togehter. + ### Modify the HTML template ### The HTML template is based on [ejs](https://github.com/mde/ejs) and located in [assets/template.ejs](assets/template.ejs) - you can apply any kind of changes. ```html - + - HTTP<%= code %> - <%= title %> - -

Hello World

-

<%= title %> Error <%= code %>

<%= message %>

- + + + <%= vars.pagetitle %> + + + +

<%= vars.title %> <%= vars.code %>

<%= vars.message %>

+ <% if (vars.footer){ %><% } %> + + ``` ### Command line options ### diff --git a/assets/template.ejs b/assets/template.ejs index 2386032..70048ea 100644 --- a/assets/template.ejs +++ b/assets/template.ejs @@ -1,17 +1,13 @@ - + -<% if (locals.page_title) { %> - <%= page_title %> -<% } else { %> - We've got some trouble | <%= code %> - <%= title %> -<% } %> - + <%= vars.pagetitle %> + -

<%= title %> <%= error %>

<%= message %>

- <% if (footer){ %><% } %> +

<%= vars.title %> <%= vars.code %>

<%= vars.message %>

+ <% if (vars.footer){ %><% } %> diff --git a/bin/generator.js b/bin/generator.js index 8e75a8e..c5d9585 100644 --- a/bin/generator.js +++ b/bin/generator.js @@ -2,82 +2,73 @@ const _fs = require('fs-magic'); const _path = require('path'); -const _assetsPath = _path.join(__dirname, '../assets'); -const _langPath = _path.join(__dirname, '../i18n'); const _cli = require('commander'); const _pkg = require('../package.json'); -const _pageRenderer = require('../lib/page-renderer'); +const _render = require('../lib/page-renderer'); const _jsonReader = require('../lib/json-reader'); +const _getResources = require('../lib/resources'); -// global paths -let templatePath = null; -let cssPath = null; +// paths +const _assetsPath = _path.join(__dirname, '../assets'); +const _langPath = _path.join(__dirname, '../i18n'); -async function generator(configFilename, pageDefinitionFile, distPath){ +// default files +const _defaults = { + distDir: _path.join(__dirname, '../dist'), + configFile: _path.join(__dirname, '../config.json'), + templateFile: _path.join(_assetsPath, 'template.ejs'), + cssFile: _path.join(_assetsPath, 'layout.css') +}; + +// generate static pages +async function generator(configFilename, distPath, opt){ // load config const config = await _jsonReader(configFilename); // load page definitions - const pages = await _jsonReader(pageDefinitionFile); + const pageDefinitions = await _jsonReader(opt.pages); - // load template - const tpl = await _fs.readFile(templatePath, 'utf8'); - - // load css - const css = await _fs.readFile(cssPath, 'utf8'); - - const lang = pageDefinitionFile.substr(-10, 2); - - console.log('Create/Empty distribution folder: "'+distPath+'"\n'); - - if(await _fs.exists(distPath)) { - await _fs.rmrf(distPath); - } - await _fs.mkdirp(distPath); + // load resources + const resources = await _getResources({ + template: opt.template, + stylesheet: opt.stylesheet, + lang: null + }); console.log('Generating static pages'); // for each errorcode generate custom page - await Promise.all(Object.keys(pages).map(async function(code){ - // page config - const pconf = pages[code]; + await Promise.all(Object.keys(pageDefinitions).map(async function(code){ + // get page config. title+message available + const pageData = pageDefinitions[code]; - // inject errorcode - pconf.code = code; + // merge variables for ejs template usage + const templateVars = Object.assign({}, pageData, { + code: code, + language: opt.lang.substr(0, 2) + }, config); - // inject lang - pconf.lang = lang || 'en'; - - // inject page title - if(config && config.page_title) { - pconf.page_title = config.page_title.replace('%code%', code); - pconf.page_title = pconf.page_title.replace('%title%', pconf.title); + // apply filter for template variable usage within the config + const varNames = Object.keys(templateVars); + const placeholderData = Object.assign({}, templateVars); + for (const key in templateVars){ + templateVars[key] = templateVars[key] && templateVars[key].replace(/%([\w]+)%/g, function(match, name){ + // name exists ? + if (varNames.includes(name)){ + return placeholderData[name]; + }else{ + throw new Error(`unknown placeholder "${name}" used in template variable [${key}]`); + } + }); } - // inject error - pconf.error = 'Error '+ pconf.code - if(config && config.error) { - pconf.error = config.error.replace('%code%', code); - } - - // inject footer - pconf.footer = pconf.footer || config.footer; - // render page - const content = await _pageRenderer(tpl, css, pconf); + const content = await _render(resources.template, resources.stylesheet, templateVars); - // generate filename - let filename = 'HTTP' + code + '.html'; - - // check filename schema - if(config && config.scheme && config.scheme.indexOf("%code%") !== -1) { - filename = config.scheme.replace('%code%', code); - } - // write content to file - await _fs.writeFile(_path.join(distPath, filename), content, 'utf8'); + await _fs.writeFile(_path.join(distPath, templateVars.scheme), content, 'utf8'); - console.log(' |- Page <' + filename + '>'); + console.log(` |- Page <${templateVars.scheme}>`); })); } @@ -89,46 +80,35 @@ _cli // static error page generator .command('static [config]') .description('run http-error-pages generator') - .option('-t, --template ', 'path to your custom EJS template file', null) - .option('-s, --styles ', 'path to your custom stylesheet (precompiled as CSS!)', null) + .option('-t, --template ', 'path to your custom EJS template file', _defaults.templateFile) + .option('-s, --styles ', 'path to your custom stylesheet (precompiled as CSS!)', _defaults.cssFile) .option('-p, --pages ', 'path to your custom page definition', null) - .option('-l, --lang ', 'the language of the default page definition', null) - .option('-o, --out ', 'output directory', null) + .option('-l, --lang ', 'the language of the default page definition', 'en_US') + .option('-o, --out ', 'output directory', _defaults.distDir) + .action(function(configFilename, options){ // config set ? - const configPath = configFilename || _path.join(__dirname, '../config.json'); - - // template path set ? - templatePath = options.template || _path.join(_assetsPath, 'template.ejs'); - - // style path set ? - cssPath = options.styles || _path.join(_assetsPath, 'layout.css'); - - // output path set ? - const distPath = options.out || _path.join(__dirname, '../dist'); - - // language set ? use en_US as default - const lang = options.lang || 'en_US'; + const configPath = configFilename || _defaults.configFile; // custom page definition available ? - let pageDefinitionFile = options.pages || null; - - // page definition not set ? use lang - if (pageDefinitionFile === null){ - pageDefinitionFile = _path.join(_langPath, 'pages-'+ lang + '.json') - } + const pageDefinitionFile = options.pages || _path.join(_langPath, 'pages-'+ options.lang + '.json'); // show paths console.log(''); console.log('Paths'); console.log(' |- Config:', configPath); - console.log(' |- Template:', templatePath); - console.log(' |- Styles:', cssPath); + console.log(' |- Template:', options.template); + console.log(' |- Styles:', options.styles); console.log(' |- Pages:', pageDefinitionFile); console.log(''); // run async generator - generator(configPath, pageDefinitionFile, distPath) + generator(configPath, options.out, { + template: options.template, + stylesheet: options.styles, + lang: options.lang, + pages: pageDefinitionFile + }) .then(function(){ console.log('Static files generated\n'); }) diff --git a/config-dist.json b/config-dist.json index 28d6617..dab23ff 100644 --- a/config-dist.json +++ b/config-dist.json @@ -1,16 +1,9 @@ { // Output Filename Scheme - eg. HTTP500.html - // %code% - HTTP error code eg. 400 "scheme": "HTTP%code%.html", // Page title (HTML not allowed) - // %code% - HTTP error code eg. 400 - // %title% - HTTP error eg. Bad Request - "page_title": "We've got some trouble | %code% - %title%", - - // Error (HTML not allowed) - // %code% - HTTP error code eg. 400 - "error": "Error %code%", + "pagetitle": "We've got some trouble | %code% - %title%", // Footer content (HTML Allowed) "footer": null diff --git a/config.json b/config.json index 41e31d5..b709d9f 100644 --- a/config.json +++ b/config.json @@ -1,17 +1,13 @@ { // Output Filename Scheme - eg. HTTP500.html - // %code% - HTTP error code eg. 400 "scheme": "HTTP%code%.html", // Page title (HTML not allowed) - // %code% - HTTP error code eg. 400 - // %title% - HTTP error eg. Bad Request - "page_title": "We've got some trouble | %code% - %title%", - - // Error (HTML not allowed) - // %code% - HTTP error code eg. 400 - "error": "Error %code%", + "pagetitle": "We've got some trouble | %code% - %title%", // Footer content (HTML Allowed) - "footer": "Tech Contact info@example.org" + "footer": "Tech Contact info@example.org", + + // additional vars + "myvar" : "Hello World" } \ No newline at end of file diff --git a/dist/HTTP400.html b/dist/HTTP400.html index a03ea7c..2373784 100644 --- a/dist/HTTP400.html +++ b/dist/HTTP400.html @@ -3,11 +3,11 @@ - We've got some trouble | 400 - Bad Request + We've got some trouble | 400 - Bad Request -

Bad Request Error 400

The server cannot process the request due to something that is perceived to be a client error.

+

Bad Request 400

The server cannot process the request due to something that is perceived to be a client error.

diff --git a/dist/HTTP401.html b/dist/HTTP401.html index 454cb05..e20641b 100644 --- a/dist/HTTP401.html +++ b/dist/HTTP401.html @@ -3,11 +3,11 @@ - We've got some trouble | 401 - Unauthorized + We've got some trouble | 401 - Unauthorized -

Unauthorized Error 401

The requested resource requires an authentication.

+

Unauthorized 401

The requested resource requires an authentication.

diff --git a/dist/HTTP403.html b/dist/HTTP403.html index 129b7aa..b795f99 100644 --- a/dist/HTTP403.html +++ b/dist/HTTP403.html @@ -3,11 +3,11 @@ - We've got some trouble | 403 - Access Denied + We've got some trouble | 403 - Access Denied -

Access Denied Error 403

The requested resource requires an authentication.

+

Access Denied 403

The requested resource requires an authentication.

diff --git a/dist/HTTP404.html b/dist/HTTP404.html index 5e4de20..414be1a 100644 --- a/dist/HTTP404.html +++ b/dist/HTTP404.html @@ -3,11 +3,11 @@ - We've got some trouble | 404 - Resource not found + We've got some trouble | 404 - Resource not found -

Resource not found Error 404

The requested resource could not be found but may be available again in the future.

+

Resource not found 404

The requested resource could not be found but may be available again in the future.

diff --git a/dist/HTTP500.html b/dist/HTTP500.html index 6662550..31d2e32 100644 --- a/dist/HTTP500.html +++ b/dist/HTTP500.html @@ -3,11 +3,11 @@ - We've got some trouble | 500 - Webservice currently unavailable + We've got some trouble | 500 - Webservice currently unavailable -

Webservice currently unavailable Error 500

An unexpected condition was encountered.
Our service team has been dispatched to bring it back online.

+

Webservice currently unavailable 500

An unexpected condition was encountered.
Our service team has been dispatched to bring it back online.

diff --git a/dist/HTTP501.html b/dist/HTTP501.html index 082b49d..5fa5d5e 100644 --- a/dist/HTTP501.html +++ b/dist/HTTP501.html @@ -3,11 +3,11 @@ - We've got some trouble | 501 - Not Implemented + We've got some trouble | 501 - Not Implemented -

Not Implemented Error 501

The Webserver cannot recognize the request method.

+

Not Implemented 501

The Webserver cannot recognize the request method.

diff --git a/dist/HTTP502.html b/dist/HTTP502.html index 91e9747..b7fba3f 100644 --- a/dist/HTTP502.html +++ b/dist/HTTP502.html @@ -3,11 +3,11 @@ - We've got some trouble | 502 - Webservice currently unavailable + We've got some trouble | 502 - Webservice currently unavailable -

Webservice currently unavailable Error 502

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

+

Webservice currently unavailable 502

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

diff --git a/dist/HTTP503.html b/dist/HTTP503.html index c2413cb..4b39ef2 100644 --- a/dist/HTTP503.html +++ b/dist/HTTP503.html @@ -3,11 +3,11 @@ - We've got some trouble | 503 - Webservice currently unavailable + We've got some trouble | 503 - Webservice currently unavailable -

Webservice currently unavailable Error 503

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

+

Webservice currently unavailable 503

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

diff --git a/dist/HTTP520.html b/dist/HTTP520.html index e3555f8..edfa66b 100644 --- a/dist/HTTP520.html +++ b/dist/HTTP520.html @@ -3,11 +3,11 @@ - We've got some trouble | 520 - Origin Error - Unknown Host + We've got some trouble | 520 - Origin Error - Unknown Host -

Origin Error - Unknown Host Error 520

The requested hostname is not routed. Use only hostnames to access resources.

+

Origin Error - Unknown Host 520

The requested hostname is not routed. Use only hostnames to access resources.

diff --git a/dist/HTTP521.html b/dist/HTTP521.html index 6d063f1..5babc41 100644 --- a/dist/HTTP521.html +++ b/dist/HTTP521.html @@ -3,11 +3,11 @@ - We've got some trouble | 521 - Webservice currently unavailable + We've got some trouble | 521 - Webservice currently unavailable -

Webservice currently unavailable Error 521

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

+

Webservice currently unavailable 521

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

diff --git a/dist/HTTP533.html b/dist/HTTP533.html index 4be8982..6d1453f 100644 --- a/dist/HTTP533.html +++ b/dist/HTTP533.html @@ -3,11 +3,11 @@ - We've got some trouble | 533 - Scheduled Maintenance + We've got some trouble | 533 - Scheduled Maintenance -

Scheduled Maintenance Error 533

This site is currently down for maintenance.
Our service team is working hard to bring it back online soon.

+

Scheduled Maintenance 533

This site is currently down for maintenance.
Our service team is working hard to bring it back online soon.

diff --git a/docs/HTTP400.html b/docs/HTTP400.html index 2181dda..840a01d 100644 --- a/docs/HTTP400.html +++ b/docs/HTTP400.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 400 - Bad Request + We've got some trouble | 400 - Bad Request -

Bad Request Error 400

The server cannot process the request due to something that is perceived to be a client error.

+

Bad Request 400

The server cannot process the request due to something that is perceived to be a client error.

diff --git a/docs/HTTP401.html b/docs/HTTP401.html index adaa4cd..169e737 100644 --- a/docs/HTTP401.html +++ b/docs/HTTP401.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 401 - Unauthorized + We've got some trouble | 401 - Unauthorized -

Unauthorized Error 401

The requested resource requires an authentication.

+

Unauthorized 401

The requested resource requires an authentication.

diff --git a/docs/HTTP403.html b/docs/HTTP403.html index 7fc22c7..5399416 100644 --- a/docs/HTTP403.html +++ b/docs/HTTP403.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 403 - Access Denied + We've got some trouble | 403 - Access Denied -

Access Denied Error 403

The requested resource requires an authentication.

+

Access Denied 403

The requested resource requires an authentication.

diff --git a/docs/HTTP404.html b/docs/HTTP404.html index 5374573..b21d098 100644 --- a/docs/HTTP404.html +++ b/docs/HTTP404.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 404 - Resource not found + We've got some trouble | 404 - Resource not found -

Resource not found Error 404

The requested resource could not be found but may be available again in the future.

+

Resource not found 404

The requested resource could not be found but may be available again in the future.

diff --git a/docs/HTTP500.html b/docs/HTTP500.html index 2b57b4a..147a0b9 100644 --- a/docs/HTTP500.html +++ b/docs/HTTP500.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 500 - Webservice currently unavailable + We've got some trouble | 500 - Webservice currently unavailable -

Webservice currently unavailable Error 500

An unexpected condition was encountered.
Our service team has been dispatched to bring it back online.

+

Webservice currently unavailable 500

An unexpected condition was encountered.
Our service team has been dispatched to bring it back online.

diff --git a/docs/HTTP501.html b/docs/HTTP501.html index 5627cb7..544b2f9 100644 --- a/docs/HTTP501.html +++ b/docs/HTTP501.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 501 - Not Implemented + We've got some trouble | 501 - Not Implemented -

Not Implemented Error 501

The Webserver cannot recognize the request method.

+

Not Implemented 501

The Webserver cannot recognize the request method.

diff --git a/docs/HTTP502.html b/docs/HTTP502.html index 2dc070e..aaf6d15 100644 --- a/docs/HTTP502.html +++ b/docs/HTTP502.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 502 - Webservice currently unavailable + We've got some trouble | 502 - Webservice currently unavailable -

Webservice currently unavailable Error 502

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

+

Webservice currently unavailable 502

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

diff --git a/docs/HTTP503.html b/docs/HTTP503.html index c9fb836..02e0236 100644 --- a/docs/HTTP503.html +++ b/docs/HTTP503.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 503 - Webservice currently unavailable + We've got some trouble | 503 - Webservice currently unavailable -

Webservice currently unavailable Error 503

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

+

Webservice currently unavailable 503

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

diff --git a/docs/HTTP520.html b/docs/HTTP520.html index b28f7b6..a151aba 100644 --- a/docs/HTTP520.html +++ b/docs/HTTP520.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 520 - Origin Error - Unknown Host + We've got some trouble | 520 - Origin Error - Unknown Host -

Origin Error - Unknown Host Error 520

The requested hostname is not routed. Use only hostnames to access resources.

+

Origin Error - Unknown Host 520

The requested hostname is not routed. Use only hostnames to access resources.

diff --git a/docs/HTTP521.html b/docs/HTTP521.html index ec1c9fd..0cede15 100644 --- a/docs/HTTP521.html +++ b/docs/HTTP521.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 521 - Webservice currently unavailable + We've got some trouble | 521 - Webservice currently unavailable -

Webservice currently unavailable Error 521

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

+

Webservice currently unavailable 521

We've got some trouble with our backend upstream cluster.
Our service team has been dispatched to bring it back online.

diff --git a/docs/HTTP533.html b/docs/HTTP533.html index e24f906..ffea9ce 100644 --- a/docs/HTTP533.html +++ b/docs/HTTP533.html @@ -1,13 +1,13 @@ - + - We've got some trouble | 533 - Scheduled Maintenance + We've got some trouble | 533 - Scheduled Maintenance -

Scheduled Maintenance Error 533

This site is currently down for maintenance.
Our service team is working hard to bring it back online soon.

+

Scheduled Maintenance 533

This site is currently down for maintenance.
Our service team is working hard to bring it back online soon.

diff --git a/docs/config.json b/docs/config.json index 120bcd4..7f1d105 100644 --- a/docs/config.json +++ b/docs/config.json @@ -1,4 +1,4 @@ { - "scheme": "HTTP%d.html", + "scheme": "HTTP%code%.html", "footer": "Technical Contact: x@example.com" } \ No newline at end of file diff --git a/examples/express.js b/examples/express.js index 6fc8495..f2adffc 100644 --- a/examples/express.js +++ b/examples/express.js @@ -17,6 +17,13 @@ async function bootstrap(){ next(myError); }); + // throw an unknown error + _webapp.get('/unknown', function(req, res, next){ + const myError = new Error(); + myError.status = 523; + next(myError); + }); + // throw an internal error _webapp.get('/500', function(req, res){ throw new Error('Server Error'); @@ -26,9 +33,20 @@ async function bootstrap(){ // because of the asynchronous file-loaders, wait until it has been executed await _httpErrorPages.express(_webapp, { lang: 'en_US', - footer: 'Hello World', - error: 'Error %code%', - page_title: "We've got some trouble | %code% - %title%", + payload: { + footer: 'Hello World', + pagetitle: 'we are sorry - an internal error encountered', + }, + filter: function(data){ + // remove footer + //data.footer = null; + return data; + }, + onError: function(data){ + // for debugging purpose only!! + // use custom middleware for errorlogging!! + console.log(`[expressjs] ERROR: ${data.title}\n${data.error.stack}`) + } }); // start service diff --git a/examples/koa.js b/examples/koa.js index 2e3ba1b..4fb0fcf 100644 --- a/examples/koa.js +++ b/examples/koa.js @@ -10,9 +10,20 @@ async function bootstrap(){ // because of the asynchronous file-loaders, wait until it has been executed - it returns an async handler _webapp.use(await _httpErrorPages.koa({ lang: 'en_US', - footer: 'Hello World', - error: 'Error %code%', - page_title: "We've got some trouble | %code% - %title%", + payload: { + footer: 'Hello World', + pagetitle: 'we are sorry - an internal error encountered', + }, + filter: function(data){ + // remove footer + //data.footer = null; + return data; + }, + onError: function(data){ + // for debugging purpose only!! + // use custom middleware for errorlogging!! + console.log(`[koa] ERROR: ${data.title}\n${data.error.stack}`) + } })); // demo handler diff --git a/lib/dispatcher.js b/lib/dispatcher.js new file mode 100644 index 0000000..491c377 --- /dev/null +++ b/lib/dispatcher.js @@ -0,0 +1,36 @@ + +// generic error dispatching +module.exports = function dispatch(opt, resources, page, httpStatusCode, err){ + // page available ? + if (!resources.pages[page]){ + // use "internal server error" as default + page = '500'; + httpStatusCode = null; + } + + // extract page date + // page found ? fallback in case http500 template not exists + const pageData = resources.pages[page] || { + title: 'Internal Server Error', + message: 'Additionally, FileNotFound was encountered while trying to use an ErrorDocument to handle the request' + }; + + // create template variables + const templateVars = Object.assign({}, { + title: pageData.title, + message: pageData.message, + code: httpStatusCode || 500, + language: opt.lang.substr(0, 2), + error: err + }, opt.payload); + + // debug error handler set ? + if (opt.onError){ + opt.onError(templateVars); + } + + return { + pageData: pageData, + templateVars: templateVars + }; +} \ No newline at end of file diff --git a/lib/express.js b/lib/express.js index aaec3d0..c804826 100644 --- a/lib/express.js +++ b/lib/express.js @@ -1,86 +1,46 @@ const _render = require('./page-renderer'); -const _path = require('path'); -const _fs = require('fs-magic'); -const _jsonReader = require('./json-reader'); +const _parseOptions = require('./options'); +const _getResources = require('./resources'); +const _dispatch = require('./dispatcher'); // wrapper to add custom options async function createDispatcher(options={}){ // merge options - const opt = { - template: options.template || _path.join(__dirname, '../assets/template.ejs'), - css: options.css || _path.join(__dirname, '../assets/layout.css'), - lang: options.lang || 'en_US', - footer: options.footer || null, - error: options.error || null, - page_title: options.page_title || null - }; + const opt = _parseOptions(options); - // load page template - const tpl = await _fs.readFile(opt.template, 'utf8'); - - // load styles - const css = await _fs.readFile(opt.css, 'utf8'); - - // load page definitions - const pages = await _jsonReader(_path.join(__dirname, '../i18n/pages-' + opt.lang + '.json')); + // load resources + const resources = await _getResources(opt); // multi-type support // @see https://github.com/expressjs/express/blob/master/examples/error-pages/index.js - return function dispatchRequest(page, httpStatusCode, req, res){ - // page available ? - if (!pages[page]){ - // use "internal server error" as default - page = '500'; - httpStatusCode = null; - } + return function dispatchRequest(page, httpStatusCode, req, res, err){ + + // run generic dispatcher + const {pageData, templateVars} = _dispatch(opt, resources, page, httpStatusCode, err); // set http status code res.status(httpStatusCode || 500); - // extract page date - const pageData = pages[page]; - - // set page title - let page_title = undefined; - if(opt && opt.page_title) { - page_title = opt.page_title.replace('%code%', page); - page_title = page_title.replace('%title%', pageData.title); - } - - // set error - let error = 'Error '+ page - if(opt && opt.error) { - error = opt.error.replace('%code%', page); - } - // multiple response formats res.format({ // standard http-error-pages html: function(){ res.type('.html'); - res.send(_render(tpl, css, { - code: httpStatusCode, - title: pageData.title, - message: pageData.message, - footer: opt.footer, - lang: opt.lang.substr(0, 2), - error: error, - page_title: page_title - })) + res.send(_render(resources.template, resources.stylesheet, opt.filter(templateVars))) }, // json json: function(){ res.json({ - error: pageData.title + ' - ' + pageData.message + error: `${pageData.title} - ${pageData.message}` }) }, // text response default: function(){ // plain text - res.type('.txt').send(pageData.title + ' - ' + pageData.message); + res.type('.txt').send(`${pageData.title} - ${pageData.message}`); } }) } @@ -93,7 +53,7 @@ module.exports = async function httpErrorPages(router, options={}){ // default 404 error - no route matches router.all('*', function(req, res){ - dispatch('404', 404, req, res); + dispatch('404', 404, req, res, new Error('file not found')); }); // internal errors (all) @@ -101,11 +61,11 @@ module.exports = async function httpErrorPages(router, options={}){ // status code given ? if (err.status){ // custom errorpage set ? - dispatch(err.errorpage || err.status, err.status, req, res); + dispatch(err.errorpage || err.status, err.status, req, res, err); // use default http500 page }else{ - dispatch('500', 500, req, res); + dispatch('500', 500, req, res, err); } }); }; \ No newline at end of file diff --git a/lib/json-reader.js b/lib/json-reader.js index f6737e1..018f122 100644 --- a/lib/json-reader.js +++ b/lib/json-reader.js @@ -1,21 +1,18 @@ const _fs = require('fs-magic'); // parse json file and allow single line comments -async function readJSONFile(filename){ +module.exports = async function readJSONFile(filename){ // load file let raw = await _fs.readFile(filename, 'utf8'); // strip single line js comments raw = raw.replace(/^\s*\/\/.*$/gm, ''); - // parse text - try { + // try to parse json + try{ return JSON.parse(raw); - } catch(e) { - console.log("Error parsing JSON file: "+filename+"\n"); + }catch(e){ + console.log(`Error parsing JSON file: [${filename}]`); throw e; } - //return JSON.parse(raw); -} - -module.exports = readJSONFile; \ No newline at end of file +} \ No newline at end of file diff --git a/lib/koa.js b/lib/koa.js index 6f99113..69122b3 100644 --- a/lib/koa.js +++ b/lib/koa.js @@ -1,59 +1,27 @@ const _render = require('./page-renderer'); -const _path = require('path'); -const _fs = require('fs-magic'); -const _jsonReader = require('./json-reader'); +const _parseOptions = require('./options'); +const _getResources = require('./resources'); +const _dispatch = require('./dispatcher'); // wrapper to add custom options async function createDispatcher(options={}){ // merge options - const opt = { - template: options.template || _path.join(__dirname, '../assets/template.ejs'), - css: options.css || _path.join(__dirname, '../assets/layout.css'), - lang: options.lang || 'en_US', - footer: options.footer || null, - error: options.error || null, - page_title: options.page_title || null - }; + const opt = _parseOptions(options); - // load page template - const tpl = await _fs.readFile(opt.template, 'utf8'); - - // load styles - const css = await _fs.readFile(opt.css, 'utf8'); - - // load page definitions - const pages = await _jsonReader(_path.join(__dirname, '../i18n/pages-' + opt.lang + '.json')); + // load resources + const resources = await _getResources(opt); // multi-type support // @see https://github.com/koajs/koa/blob/master/docs/api/response.md#responseistypes - return function dispatchRequest(page, httpStatusCode, ctx){ - // page available ? - if (!pages[page]){ - // use "internal server error" as default - page = '500'; - httpStatusCode = null; - } + return function dispatchRequest(page, httpStatusCode, ctx, err){ + + // run generic dispatcher + const {pageData, templateVars} = _dispatch(opt, resources, page, httpStatusCode, err); // set http status code ctx.status = httpStatusCode || 500; - // extract page date - const pageData = pages[page]; - - // set page title - let page_title = undefined; - if(opt && opt.page_title) { - page_title = opt.page_title.replace('%code%', page); - page_title = page_title.replace('%title%', pageData.title); - } - - // set error - let error = 'Error '+ page - if(opt && opt.error) { - error = opt.error.replace('%code%', page); - } - // multiple response formats switch (ctx.accepts('json', 'html', 'text')){ @@ -61,28 +29,20 @@ async function createDispatcher(options={}){ case 'json': ctx.type = 'json'; ctx.body = { - error: pageData.title + ' - ' + pageData.message + error: `${pageData.title} - ${pageData.message}` } break; // html response case 'html': ctx.type = 'html'; - ctx.body = _render(tpl, css, { - code: httpStatusCode, - title: pageData.title, - message: pageData.message, - footer: opt.footer, - lang: opt.lang.substr(0, 2), - error: error, - page_title: page_title - }); + ctx.body = _render(resources.template, resources.stylesheet, opt.filter(templateVars)); break; // default: text response default: ctx.type = 'text/plain'; - ctx.body = pageData.title + ' - ' + pageData.message; + ctx.body = `${pageData.title} - ${pageData.message}`; } } } @@ -101,17 +61,17 @@ module.exports = async function httpErrorPages(options={}){ // route not found ? if (ctx.status === 404) { - dispatch('404', 404, ctx); + dispatch('404', 404, ctx, new Error('file not found')); } }catch(err){ // status code given ? if (err.status){ // custom errorpage set ? - dispatch(err.errorpage || err.status, err.status, ctx); + dispatch(err.errorpage || err.status, err.status, ctx, err); // use default http500 page }else{ - dispatch('500', 500, ctx); + dispatch('500', 500, ctx, err); } } } diff --git a/lib/options.js b/lib/options.js new file mode 100644 index 0000000..ff7f5ac --- /dev/null +++ b/lib/options.js @@ -0,0 +1,17 @@ +const _path = require('path'); + +// wrapper to add custom options +module.exports = function getOptions(opt){ + + // merge options + const options = { + template: opt.template || _path.join(__dirname, '../assets/template.ejs'), + stylesheet: opt.css || _path.join(__dirname, '../assets/layout.css'), + lang: opt.lang || 'en_US', + payload: opt.payload || {}, + filter: opt.filter || function(d){return d}, + onError: opt.onError || null + }; + + return options; +} \ No newline at end of file diff --git a/lib/page-renderer.js b/lib/page-renderer.js index fd29570..87c19cf 100644 --- a/lib/page-renderer.js +++ b/lib/page-renderer.js @@ -1,13 +1,25 @@ const _ejs = require('ejs'); // render template using given data -function renderTemplate(template, css, data={}){ - +module.exports = function renderTemplate(template, css, data={}){ + + // pagetitle set ? + if (!data.pagetitle){ + // use default title + data.pagetitle = `We've got some trouble | ${data.code} - ${data.title}`; + } + // assign css data.inlinecss = css; - // render template - use custom escape function to handle linebreaks! + // render template return _ejs.render(template, data, { + // wrap all variables into "vars" object + strict: true, + _with: false, + localsName: 'vars', + + // use custom escape function to handle linebreaks! escape: function(text){ if (!text){ return ''; @@ -22,6 +34,4 @@ function renderTemplate(template, css, data={}){ return text; } }); -} - -module.exports = renderTemplate; \ No newline at end of file +} \ No newline at end of file diff --git a/lib/resources.js b/lib/resources.js new file mode 100644 index 0000000..3e84fa7 --- /dev/null +++ b/lib/resources.js @@ -0,0 +1,22 @@ +const _path = require('path'); +const _fs = require('fs-magic'); +const _jsonReader = require('./json-reader'); + +// wrapper to add custom options +module.exports = async function loadResources(opt){ + + // load page template + const template = opt.template && await _fs.readFile(opt.template, 'utf8'); + + // load styles + const stylesheet = opt.stylesheet && await _fs.readFile(opt.stylesheet, 'utf8'); + + // load page definitions + const pages = opt.lang && await _jsonReader(_path.join(__dirname, '../i18n/pages-' + opt.lang + '.json')); + + return { + template: template, + stylesheet: stylesheet, + pages: pages + }; +} \ No newline at end of file diff --git a/package.json b/package.json index 186e364..174ae82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http-error-pages", - "version": "2.0.1", + "version": "3.0.0", "description": "Simple HTTP Error Pages. standalone + static + koa + express", "engines": { "node": ">=7.6" @@ -36,10 +36,10 @@ "http-error-pages": "./bin/generator.js" }, "main": "./lib/main.js", - "author": "Andi Dittrich (https://andidittrich.de)", - "homepage": "https://github.com/AndiDittrich/HttpErrorPages", - "bugs": "https://github.com/AndiDittrich/HttpErrorPages/issues", - "repository": "AndiDittrich/HttpErrorPages", + "author": "Andi Dittrich (https://andidittrich.com)", + "homepage": "https://github.com/HttpErrorPages/HttpErrorPages", + "bugs": "https://github.com/HttpErrorPages/HttpErrorPages/issues", + "repository": "HttpErrorPages/HttpErrorPages", "license": "MIT", "devDependencies": { "commander": "^6.2.0",