MaddHacker/server-lite

View on GitHub
lib/sl-handler.js

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * Copyright 2017 MaddHacker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const path = require('path');
const url = require('url');

const stringz = require('string-utilz');

const con = require('./sl-content');

/**
 * Set of request handler methods that should be useful for whomever consumes this npm.
 */
class slHandler {
    /**
     * Takes an instance of slUtils for output and other utilities
     * 
     * @param {slUtils}
     * @param {string} _webappRoot
     * @param {string} _indexPath
     * @param {string} _concatJsFolderPath
     * @param {string} _concatCssFolderPath
     * 
     * @see slUtils
     */
    constructor(utilz, root, index, concatJs, concatCss) {
        this.utilz = utilz || (new (require('./sl-utils'))());
        /* root of the webapp to serve from */
        this._webappRoot = (root || '');
        /* index file */
        this._indexPath = (index || '');
        /* folder where concat js targets live */
        this._concatJsFolderPath = (concatJs || '');
        /* folder where concat css targets live */
        this._concatCssFolderPath = (concatCss || '');
    }

    /**
     * Dumps the properties of this object to the logs at info level
     */
    _logInfo() {
        this.o.d('Utilz: ' + JSON.stringify(this.utilz));
        this.o.d('WebappRoot: ' + this._webappRoot);
        this.o.d('IndexPath: ' + this._indexPath);
        this.o.d('ConcatJsFolderPath: ' + this._concatJsFolderPath);
        this.o.d('ConcatCssFolderPath: ' + this._concatCssFolderPath);
        return true;
    }

    /**
     * Internal shortcut to get the output handle
     * 
     * @return {output-manager} instance
     */
    get o() { return this.utilz._out; }

    /**
     * Get where the webappRoot (on the filesystem) is at the moment
     * 
     * This should be relative to the app root without the leading or trailing slashes
     * 
     * @return {string}
     */
    get webRoot() { return this._webappRoot; }

    /**
     * set a new webappRoot (on the filesystem)
     * 
     * @param {string} root => This should be relative to the app root without the leading or trailing slashes
     */
    set webRoot(root) { this._webappRoot = root; }

    /**
     * Path to the index.html file (relative to the webRoot)
     * 
     * @return {string}
     */
    get indexPath() { return this._indexPath; }

    /**
     * set a new path to the index.html file (relative to the webRoot)
     * 
     * @param {string} path => typically starting with a '/'
     */
    set indexPath(path) { this._indexPath = path; }

    /**
     * path (relative to webRoot) where JavaScript files live that should/can be concatenated
     * 
     * @return {string}
     */
    get concatenateJavscriptFolderPath() { return this._concatJsFolderPath; }

    /**
     * path (relative to webRoot) where JavaScript files live that should/can be concatenated
     * 
     * @param {string}
     */
    set concatenateJavscriptFolderPath(path) { this._concatJsFolderPath = path; }

    /**
     * path (relative to webRoot) where CSS files live that should/can be concatenated
     * 
     * @return {string}
     */
    get concatenateCssFolderPath() { return this._concatCssFolderPath; }

    /**
     * path (relative to webRoot) where CSS files live that should/can be concatenated
     * 
     * @param {string}
     */
    set concatenateCssFolderPath(path) { this._concatCssFolderPath = path; }

    /**
     * Simple helper to parse the urlPath from the request
     * 
     * @param {Http.Request}
     * @return {string}
     */
    _getUrlPath(request) { return url.parse(request.url).pathname; }

    /**
     * simple helper to parse the query object from the request
     * 
     * @param {Http.Request}
     * @return {string}
     */
    _getQuery(request) { return url.parse(request.url, true).query; }

    /**
     * Loads a file and sends it back based on the filesystem
     * 
     * @see {slUtils} #respondWithFileFromPath(filePath,response)}
     */
    simpleFileBasedWebServer(request, response) {
        this.o.t('Called simpleFileBasedWebServer');
        let urlPath = this._getUrlPath(request);
        this.o.d('[simpleFileBasedWebServer] urlPath: ' + urlPath);
        let filePath = this._webappRoot + ((urlPath == '/') ? this._indexPath : urlPath);
        this.o.d('[simpleFileBasedWebServer] filePath: ' + filePath);
        this.utilz.respondWithFileFromPath(filePath, response);
    }

    /**
     * Responds with the concatenated JS file requested in {Http.Request}
     * 
     * @see #concatenateAll
     */
    concatenatedJavscript(request, response) {
        this.o.t('Called concatenatedJavscript');
        this.concatenateAll(request, response, this._concatJsFolderPath, '.js');
    }

    /**
     * Responds with the concatenated CSS file requested in {Http.Request}
     * 
     * @see #concatenateAll
     */
    concatenatedCss(request, response) {
        this.o.t('Called concatenatedCss');
        this.concatenateAll(request, response, this._concatCssFolderPath, '.css');
    }

    /**
     * Does the work of concatenating a series of files as defined in the URL.
     * 
     * Typically a url will look like:
     *      'concat.js?files=a,b,c,d'
     *      'concat.css?files=a,b,c,d'
     * 
     * In each case, the files will be concatenated IN ORDER of request.  This will then return one
     * BIG file as part of the request.
     * 
     * @param {Http.Request}
     * @param {Http.Response}
     * @param {string} folder => folder (relative to webRoot) where all the files to concatenate live
     * @param {string} ext => type of files (e.g. '.js' or '.css' => NOTE: the '.' matters!)
     */
    concatenateAll(request, response, folder, ext) {
        this.o.t(stringz.fmt('Called concatenateAll (%{s})', ext));
        let query = this._getQuery(request);
        if (query && query.files) {
            this.o.d(stringz.fmt('[concatenateAll (%{0})] query: "%{1}"', ext, JSON.stringify(query)));
            let baseDir = this._webappRoot + folder;
            let files = query.files.split(',');
            this.o.d(stringz.fmt('Loading %{0} files from %{1} with extension "%{2}" (%{3})', files.length, folder, ext, files));
            let allData = {};
            // async load all file info
            files.forEach((file) => {
                let tmpPath = this._webappRoot + folder + file + ext;
                this.o.d(stringz.fmt('Trying to load file "%{s}"', tmpPath));
                this.utilz.loadDataFromFile(tmpPath).then((data) => {
                    this.o.d(stringz.fmt('Loaded file data from "%{0}"', tmpPath));
                    allData[file] = data;
                }, (err) => {
                    this.o.w(stringz.fmt('File at "%{0}" does not exist or could not be read. MSG: "%{1}"', tmpPath, err));
                    allData[file] = '';
                });
            });

            // wait for all async processes to finish
            let awaitAll = () => {
                if (Object.keys(allData).length != files.length) { setTimeout(awaitAll, 50); }
                else {
                    let tmpData = '';
                    // load all data in order
                    files.forEach((file) => {
                        this.o.d(stringz.fmt('Adding data from %{0}%{1}', file, ext));
                        this.o.t('==============DATA==============');
                        this.o.t(allData[file]);
                        this.o.t('============END DATA============');
                        tmpData += allData[file];
                    });
                    this.utilz.writeResponse(response, 200, con.byExtension(stringz.chop(ext, -1), tmpData));
                }
            };

            // kick off the await...
            awaitAll();
        } else {
            this.o.w(stringz.fmt('[concatenateAll (%{s})] requested a concat but did not provide files!', ext));
            this.utilz.writeResponse(response, 404, con.text(stringz.fmt('Requested a concatenated %{s} file but did not provide files to concat!', ext)));
        }
    }
}

module.exports = slHandler;