function NodeActionService()

in knative-build/runtimes/javascript/src/service.js [23:238]


function NodeActionService(config) {

    var Status = {
        ready: 'ready',
        starting: 'starting',
        running: 'running',
        stopped: 'stopped',
    };

    // TODO: save the entire configuration for use by any of the route handlers
    var status = Status.ready;
    var ignoreRunStatus = config.allowConcurrent === undefined ? false : config.allowConcurrent.toLowerCase() === 'true';
    var server = undefined;
    var userCodeRunner = undefined;
    DEBUG.trace('Initialize: status=' + status);
    DEBUG.trace('Initialize: ignoreRunStatus=' + ignoreRunStatus);

    function setStatus(newStatus) {
        DEBUG.functionStart('newStatus=' + newStatus + ' (oldStatus=' + status + ')');
        if (status !== Status.stopped) {
            status = newStatus;
        }
        DEBUG.functionEnd('status=' + status);
    }

    /**
     * An ad-hoc format for the endpoints returning a Promise representing,
     * eventually, an HTTP response.
     *
     * The promised values (whether successful or not) have the form:
     * { code: int, response: object }
     *
     */
    function responseMessage(code, response) {
        return { code: code, response: response };
    }

    function errorMessage(code, errorMsg) {
        return responseMessage(code, { error: errorMsg });
    }

    /**
     * Indicates if we have been initialized which is determined by if we have
     * created a NodeActionRunner.
     * @returns {boolean}
     */
    this.initialized = function isInitialized(){
        return (typeof userCodeRunner !== 'undefined');
    };

    /**
     * Starts the server.
     *
     * @param app express app
     */
    this.start = function start(app) {
        DEBUG.functionStart();
        server = app.listen(config.port, function() {
            var host = server.address().address;
            var port = server.address().port;
            DEBUG.trace('listening: host: [' + host + '], port: [' + port + ']', 'Express (callback)');
        });

        // This is required as http server will auto disconnect in 2 minutes, this to not auto disconnect at all
        server.timeout = 0;
        DEBUG.dumpObject(server, 'server');
        DEBUG.functionEnd();
    };

    /** Returns a promise of a response to the /init invocation.
     *
     *  req.body = { main: String, code: String, binary: Boolean }
     */
    this.initCode = function initCode(req) {
        DEBUG.functionStart('status=' + status);

        if (status === Status.ready && userCodeRunner === undefined) {

            setStatus(Status.starting);

            var body = req.body || {};
            var message = body.value || {};
            DEBUG.dumpObject(body, 'body');
            DEBUG.dumpObject(message, 'message');

            if (message.main && message.code && typeof message.main === 'string' && typeof message.code === 'string') {
                return doInit(message).then(function(result) {
                    setStatus(Status.ready);
                    DEBUG.dumpObject(result, 'result', 'initCode');
                    DEBUG.functionEndSuccess('[200] { OK: true }', 'initCode');
                    return responseMessage(200, { OK: true });
                }).catch(function(error) {
                    setStatus(Status.stopped);
                    var errStr = 'Initialization has failed due to: ' + error.stack ? String(error.stack) : error;
                    DEBUG.functionEndError('[502] ' + errStr, 'initCode');
                    return Promise.reject(errorMessage(502, errStr));
                });
            } else {
                setStatus(Status.ready);
                var msg = 'Missing main/no code to execute.';
                DEBUG.functionEndError('[403] ' + msg);
                return Promise.reject(errorMessage(403, msg));
            }
        } else if (userCodeRunner !== undefined) {
            var msg = 'Cannot initialize the action more than once.';
            console.error('Internal system error:', msg);
            DEBUG.functionEndError('[403] ' + msg);
            return Promise.reject(errorMessage(403, msg));
        } else {
            var msg = 'System not ready, status is ' + status + '.';
            console.error('Internal system error:', msg);
            DEBUG.functionEndError('[403] ' + msg);
            return Promise.reject(errorMessage(403, msg));
        }
    };

    /**
     * Returns a promise of a response to the /exec invocation.
     * Note that the promise is failed if and only if there was an unhandled error
     * (the user code threw an exception, or our proxy had an internal error).
     * Actions returning { error: ... } are modeled as a Promise successful resolution.
     *
     * req.body = { value: Object, meta { activationId : int } }
     */
    this.runCode = function runCode(req) {
        DEBUG.functionStart('status=' + status);
        DEBUG.dumpObject(req, 'request');
        if (status === Status.ready) {
            if (!ignoreRunStatus) {
                setStatus(Status.running);
            }

            return doRun(req).then(function(result) {
                if (!ignoreRunStatus) {
                    setStatus(Status.ready);
                }
                DEBUG.dumpObject(result, 'result', 'runCode');
                if (typeof result !== 'object') {
                    DEBUG.functionEndError('[502] The action did not return a dictionary.', 'runCode');
                    return errorMessage(502, 'The action did not return a dictionary.');
                } else {
                    DEBUG.functionEndSuccess('[200] result: ' + result, 'runCode');
                    return responseMessage(200, result);
                }
            }).catch(function(error) {
                var msg = 'An error has occurred: ' + error;
                setStatus(Status.stopped);
                DEBUG.functionEndError('[502]: ' + msg);
                return Promise.reject(errorMessage(502, msg));
            });
        } else {
            var msg = 'System not ready, status is ' + status + '.';
            console.error('Internal system error:', msg);
            DEBUG.functionEndError('[403] ' + msg);
            return Promise.reject(errorMessage(403, msg));
        }
    };

    function doInit(message) {
        userCodeRunner = new NodeActionRunner();

        DEBUG.functionStart();
        DEBUG.dumpObject(message, 'message');
        return userCodeRunner.init(message).then(function(result) {
            // 'true' has no particular meaning here. The fact that the promise
            // is resolved successfully in itself carries the intended message
            // that initialization succeeded.
            DEBUG.functionEndSuccess('userCodeRunner.init() Success');
            return true;
        }).catch(function(error) {
            // emit error to activation log then flush the logs as this
            // is the end of the activation
            console.error('Error during initialization:', error);
            writeMarkers();
            DEBUG.functionEndError('userCodeRunner.init() Error: ' + error);
            return Promise.reject(error);
        });
    }

    function doRun(req) {
        DEBUG.functionStart();
        DEBUG.dumpObject(req, 'request');
        var msg = req && req.body || {};
        DEBUG.dumpObject(msg, 'msg');
        DEBUG.trace('Adding process environment variables:');
        // Move per-activation keys to process env. vars with __OW_ (reserved) prefix
        Object.keys(msg).forEach(
            function(k) {
                if (typeof msg[k] === 'string' && k !== 'value'){
                    var envVariable = '__OW_' + k.toUpperCase();
                    process.env['__OW_' + k.toUpperCase()] = msg[k];
                    DEBUG.dumpObject(process.env[envVariable], envVariable, 'doRun');
                }
            }
        );

        return userCodeRunner.run(msg.value).then(function(result) {
            if (typeof result !== 'object') {
                console.error('Result must be of type object but has type "' + typeof result + '":', result);
            }
            writeMarkers();
            DEBUG.functionEndSuccess('userCodeRunner.run(): Success (' + result + ')', 'doRun');
            return result;
        }).catch(function(error) {
            console.error(error);
            writeMarkers();
            DEBUG.functionEndError('userCodeRunner.run(): Error:' + error, 'doRun');
            return Promise.reject(error);
        });
    }

    function writeMarkers() {
        console.log('XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX');
        console.error('XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX');
    }
}