Framework Notes (serverStart)

Throwing the alphabetical approach out the window, there’s file that I’m pretty interested in looking at. And that file is ServerStart.js. This file get’s loaded when a new Jaxer instance is created in order to initialize the Apps that are recognized for that instance of the server. So getting familiar with this file might help with either simplifying or reducing the number of files needed to run Jaxer.

/* ***** LICENSE BLOCK *****
 * Version: GPL 3
 *
 * This program is Copyright (C) 2007-2008 Aptana, Inc. All Rights Reserved
 * This program is licensed under the GNU General Public license, version 3 (GPL).
 *
 * This program is distributed in the hope that it will be useful, but
 * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
 * NONINFRINGEMENT. Redistribution, except as permitted by the GPL,
 * is prohibited.
 *
 * You can redistribute and/or modify this program under the terms of the GPL, 
 * as published by the Free Software Foundation.  You should
 * have received a copy of the GNU General Public License, Version 3 along
 * with this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Aptana provides a special exception to allow redistribution of this file
 * with certain other code and certain additional terms
 * pursuant to Section 7 of the GPL. You may view the exception and these
 * terms on the web at http://www.aptana.com/legal/gpl/.
 * 
 * You may view the GPL, and Aptana's exception and additional terms in the file
 * titled license-jaxer.html in the main distribution folder of this program.
 * 
 * Any modifications to this file must keep this entire header intact.
 *
 * ***** END LICENSE BLOCK ***** */

(function() {

    var log = Log.forModule("ServerStart");

    CoreEvents.clearHandlers(CoreEvents.SERVER_START);

    try {
        CoreEvents.addHandler(CoreEvents.SERVER_START, function onServerStart(evt) {


            if (Config.RESPONSE_ERROR_PAGE) {
                Jaxer.responseErrorPage = File.read(Config.RESPONSE_ERROR_PAGE);
            }
            if (Jaxer.responseErrorPage == null) {
                Jaxer.responseErrorPage = '<html><head><title>Error</title></head><body><h2>Error processing your request</h2> This server is configured to not display errors on the browser. Further information may have been logged on the server.</body></html>';
            }

            if (Config.FATAL_ERROR_PAGE) {
                try {
                    Jaxer.fatalErrorPage = File.read(Config.FATAL_ERROR_PAGE);
                } catch (e) {
                    Jaxer.fatalErrorPage = null;
                }
            }
            if (Jaxer.fatalErrorPage == null) {
                Jaxer.fatalErrorPage = '<html><head><title>Server Error</title></head><body><h2>Server Error</h2> This server is configured to not display errors on the browser. Further information may have been logged on the server.</body></html>';
            }

            if (!Config.FRAMEWORK_DB) {
                var error = new Exception("The database connection parameters for the Jaxer framework have not been specified", log);
                Jaxer.notifyFatal(error);
                throw error;
            }

            try {
                loadConfigApps(Config.FRAMEWORK_DIR);
                if (typeof Config.LOCAL_CONF_DIR == "string") {
                    loadConfigApps(Config.LOCAL_CONF_DIR);
                }
                if (!(Config.DEFAULT_APP instanceof App)) Config.DEFAULT_APP = new App(Config.DEFAULT_APP);
                if (!Config.apps || !(Config.apps instanceof Config.apps.__parent__.Array)) throw "Config.apps is not defined or is not an Array; please check all configApps.js files";
                Config.apps.forEach(function(app, index) {
                    if (!app) throw "The app at position " + index + " of Config.apps is invalid; please check all configApps.js files";
                    switch (typeof app.name) {
                        case "string":
                            var appName = String(app.name);
                            if (appName == Config.DEFAULT_APP.name) throw "Cannot use application name of '" + appName + "' in Config.apps (it's reserved for the default); please check all configApps.js files";
                            if (Config.appsByName[appName]) throw "Duplicate name '" + appName + "' found in Config.apps; please check all configApps.js files";
                            Config.appsByName[appName] = new App(app);
                            break;
                        case "function":
                            break;
                        default:
                            throw "The app at position " + index + " of Config.apps has no name or name function; please check all configApps.js files";
                    }
                });

                DB.init();

                log.trace("About to create callbacks schema");
                createCallbacksSchema();

                log.trace("About to create container schema");
                Container.DBPersistor.createSchema();

                if (Config.EMBEDDED_CLIENT_FRAMEWORK_SRC) {
                    log.trace("Reading embedded client framework source from: " + Config.EMBEDDED_CLIENT_FRAMEWORK_SRC);
                    Jaxer.embeddedClientFramework = File.read(Config.EMBEDDED_CLIENT_FRAMEWORK_SRC);;
                    log.trace("Embedded client framework is " + Jaxer.embeddedClientFramework.length + " characters long");
                } else {
                    Jaxer.embeddedClientFramework = null;
                }

                Jaxer.loadAllExtensions();

            } catch (e) {
                var error = new Exception(e, log);
                Jaxer.notifyFatal(error);
                throw error;
            }

        });
    } catch (ex) {
        throw new Exception("Could not add handler: " + ex.description, log);
    }

    function createCallbacksSchema() {
        sql = "CREATE TABLE IF NOT EXISTS callback_page (" +
            " id INTEGER PRIMARY KEY AUTO_INCREMENT," +
            " crc32 INT(11) DEFAULT NULL," +
            " name VARCHAR(255) DEFAULT NULL," +
            " document_root VARCHAR(255) DEFAULT NULL," +
            " page_file VARCHAR(255) DEFAULT NULL," +
            " value LONGTEXT," +
            " creation_datetime DATETIME DEFAULT NULL," +
            " access_datetime DATETIME DEFAULT NULL," +
            " access_count INT(11) DEFAULT 0" +
            ")";
        DB.frameworkExecute(sql);
    }

    function loadConfigApps(folderSpec) {
        var defaultRootUrl = Dir.pathToUrl(System.executableFolder);
        var absConfUrl = Web.resolve(folderSpec, defaultRootUrl);
        var confPathOnDisk = Dir.urlToPath(absConfUrl);
        var confFiles = Dir.grep(confPathOnDisk, {
            pattern: '\\bconfigApps.js$',
            recursive: false
        });
        confFiles.sort(function(f1, f2) {
            if (f1 < f2)
                return -1;
            if (f2 < f1)
                return 1;
            return 0;
        });
        confFiles.forEach(function loadConfigApp(confFile) {
            var confFileUrl = Jaxer.Dir.pathToUrl(confFile.path);
            Jaxer.include(confFileUrl);
        });

    }

    Log.trace("*** ServerStart.js loaded");

})();

In this file we see a lot of low hanging fruit that can be simplified. For starters we try to read static files for error pages defined in the configuration, and if they don’t exist we use default pages. I think we could at least turn this around and use default strings, and then attempt to read the files if they exist.

The framework then attempts to read all of the ‘configApps.js‘ files from the configuration and attempts to initialize all of the Apps on the instance of the server. If we can figure out how to remove all of these files, we could attempts to use Jaxer to use only the default configuration (potentially defined in this file) and then skip the configuration files all together.

One of the potentially interesting aspects of reading this file is the the embeddedClientFramework aspect. From what I’ve seen so far, I don’t think this property actually gets used, as I think Jaxer adds a script reference to the client framework and doesn’t include the client-side script as an embedded script element into the source. And that’s the preferable implementation. But more importantly it’s more interesting that Jaxer reads and has the source code saved.

This goes to implementation. Right now Jaxer writes the parameters to the server-side defined callback functions as an embedded script in the resulting page, and then includes the script to the static client side code. What I think would be a cleaner approach would be to add a GET query parameter after the client-side code source included on the client. And then when the client-side script is loaded, Jaxer would then create the server-side callback functions directly in the client script. This would effectively remove the embedded code that currently gets written in the head as embedded code.

What’s also interesting is the included createCallbacksSchema function, as it provides the table template for the server-side code that gets cached on the server after it has been read. It makes me wonder about what the simplest implementation for the server-side caching should be. Ideally I want to isolate the framework functionality from any user-defined databases.

This means that I’m really leaning towards the option where the server-side code is cached in an Sqlite database and application data is included in a MariaDB instance. Though I’m also interested in a potential Redis implementation, where the server-side byte code and cookie data could be cached in an in-memory database to make fetching code faster than reading from the file system. So we’ll have to keep an eye out to see if the table structure can be effectively implemented in a key-value structure.