소스 검색

refactored codebase

Andi Dittrich 4 년 전
부모
커밋
e2a2b2b98b

+ 17 - 0
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
 ------------------------------------------------------
 

+ 57 - 18
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 <strong>World</strong>'
+        payload: {
+            footer: 'Hello <strong>World</strong>',
+            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 <strong>World</strong>'
+    payload: {
+        footer: 'Hello <strong>World</strong>',
+        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 <a href=\"mailto:info@example.org\">info@example.org</a>"
@@ -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 <a href="mailto:info@example.org">info@example.org</a>',
+  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
 <!DOCTYPE html>
-<html lang="en">
+<html lang="<%= vars.language %>">
 <head>
-    <title>HTTP<%= code %> - <%= title %></title>
-</head><body>
-    <h2>Hello World</h2>
-    <div class="cover"><h1><%= title %> <small>Error <%= code %></small></h1><p class="lead"><%= message %></p></div>
-</body></html>
+    <!-- Simple HttpErrorPages | MIT License | https://github.com/HttpErrorPages -->
+    <meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1" />
+    <title><%= vars.pagetitle %></title>
+    <style type="text/css"><%- vars.inlinecss %></style>
+</head>
+<body>
+    <div class="cover"><h1><%= vars.title %> <small><%= vars.code %></small></h1><p class="lead"><%= vars.message %></p></div>
+    <% if (vars.footer){ %><footer><p><%- vars.footer %></p></footer><% } %>
+</body>
+</html>
 ```
 
 ### Command line options ###

+ 5 - 9
assets/template.ejs

@@ -1,17 +1,13 @@
 <!DOCTYPE html>
-<html lang="<%= lang %>">
+<html lang="<%= vars.language %>">
 <head>
     <!-- Simple HttpErrorPages | MIT License | https://github.com/HttpErrorPages -->
     <meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1" />
-<% if (locals.page_title) { %>
-    <title><%= page_title %></title>
-<% } else { %>
-    <title>We've got some trouble | <%= code %> - <%= title %></title>
-<% } %>
-    <style type="text/css"><%- inlinecss %></style>
+    <title><%= vars.pagetitle %></title>
+    <style type="text/css"><%- vars.inlinecss %></style>
 </head>
 <body>
-    <div class="cover"><h1><%= title %> <small><%= error %></small></h1><p class="lead"><%= message %></p></div>
-    <% if (footer){ %><footer><p><%- footer %></p></footer><% } %>
+    <div class="cover"><h1><%= vars.title %> <small><%= vars.code %></small></h1><p class="lead"><%= vars.message %></p></div>
+    <% if (vars.footer){ %><footer><p><%- vars.footer %></p></footer><% } %>
 </body>
 </html>

+ 62 - 82
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);
-
-    // load template
-    const tpl = await _fs.readFile(templatePath, 'utf8');
-   
-    // load css
-    const css = await _fs.readFile(cssPath, 'utf8');
-
-    const lang = pageDefinitionFile.substr(-10, 2);
+    const pageDefinitions = await _jsonReader(opt.pages);
 
-    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];
-
-        // inject errorcode
-        pconf.code = code;
-
-        // 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);
-        }
-
-        // inject error
-        pconf.error = 'Error '+ pconf.code 
-        if(config && config.error) {
-            pconf.error = config.error.replace('%code%', code);
+    await Promise.all(Object.keys(pageDefinitions).map(async function(code){
+        // get page config. title+message available
+        const pageData = pageDefinitions[code];
+
+        // merge variables for ejs template usage
+        const templateVars = Object.assign({}, pageData, {
+            code: code,
+            language: opt.lang.substr(0, 2)
+        }, config);
+
+        // 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 footer
-        pconf.footer = pconf.footer || config.footer;
-
         // render page
-        const content = await _pageRenderer(tpl, css, pconf);
-
-        // generate filename
-        let filename = 'HTTP' + code + '.html';
+        const content = await _render(resources.template, resources.stylesheet, templateVars);
 
-        // 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>', 'path to your custom EJS template file', null)
-    .option('-s, --styles <path>', 'path to your custom stylesheet (precompiled as CSS!)', null)
+    .option('-t, --template <path>', 'path to your custom EJS template file', _defaults.templateFile)
+    .option('-s, --styles <path>', 'path to your custom stylesheet (precompiled as CSS!)', _defaults.cssFile)
     .option('-p, --pages <path>', 'path to your custom page definition', null)
-    .option('-l, --lang <lang>', 'the language of the default page definition', null)
-    .option('-o, --out <path>', 'output directory', null)
+    .option('-l, --lang <lang>', 'the language of the default page definition', 'en_US')
+    .option('-o, --out <path>', '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');
             })

+ 1 - 8
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

+ 5 - 9
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 <a href=\"mailto:info@example.org\">info@example.org</a>"
+    "footer": "Tech Contact <a href=\"mailto:info@example.org\">info@example.org</a>",
+
+    // additional vars
+    "myvar" : "Hello World"
 }

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP400.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP401.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP403.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP404.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP500.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP501.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP502.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP503.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP520.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP521.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/HTTP533.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP400.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP401.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP403.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP404.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP500.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP501.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP502.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP503.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP520.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP521.html


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
docs/HTTP533.html


+ 1 - 1
docs/config.json

@@ -1,4 +1,4 @@
 {
-    "scheme": "HTTP%d.html",
+    "scheme": "HTTP%code%.html",
     "footer": "Technical Contact: <a href=\"mailto:x@example.com\">x@example.com</a>"
 }

+ 21 - 3
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 <strong>World</strong>',
-        error: 'Error %code%',
-        page_title: "We've got some trouble | %code% - %title%",
+        payload: {
+            footer: 'Hello <strong>World</strong>',
+            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

+ 14 - 3
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 <strong>World</strong>',
-        error: 'Error %code%',
-        page_title: "We've got some trouble | %code% - %title%",
+        payload: {
+            footer: 'Hello <strong>World</strong>',
+            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

+ 36 - 0
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
+    };
+}

+ 16 - 56
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);
         }
     });
 };

+ 6 - 9
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;
+}

+ 16 - 56
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);
             }
         }
     }

+ 17 - 0
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;
+}

+ 16 - 6
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;
+}

+ 22 - 0
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
+    };
+}

+ 5 - 5
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",

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.