<commit_before>/*
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */


/*jslint anon:true, sloppy:true, nomen:true*/


/**
 * This is an object used as the single pathway for data to leave a mojit
 * action execution. It is used as a component of the ActionContext object,
 * which uses it to call <em>done</em> and <em>flush</em> in order to complete.
 *
 * There are two versions of this object, one for the client, and one for the
 * server. This is the server version, which is more complex than the client
 * version.
 *
 * @class OutputHandler
 * @param {Object} req The Request object.
 * @param {Object} res The Response object.
 * @param {Function} next The next function, which should be invokable.
 * @constructor
 */
var NAME = 'OutputHandler.server',
    OutputHandler = function(req, res, next) {
        this.req = req;
        this.res = res;
        this.next = next;
        this.headers = {};
    };


OutputHandler.prototype = {

    setLogger: function(logger) {
        this.logger = logger;
    },

    setMemoryDebugObjects: function(objs) {
        this.memDebugObjects = objs;
    },


    flush: function(data, meta) {
        this._readMeta(meta);
        this._writeHeaders();
        this.res.write(data);
    },


    done: function(data, meta) {
        var name,
            obj,
            size,
            memDebug = {};

        this.logger.log('done', 'info', NAME);
        this._readMeta(meta);
        this._writeHeaders();
        if (!data ||
                (typeof data !== 'string' && Object.keys(data).length === 0)) {
            data = '';
        }
        this.res.end(data);
        this.logger.log('END', 'mojito', NAME);

        // special memory debugging
        if (this.memDebugObjects) {
            for (name in this.memDebugObjects) {
                if (this.memDebugObjects.hasOwnProperty(name)) {
                    obj = this.memDebugObjects[name];
                    try {
                        size = this._stringify(obj, [name]).length;
                    } catch (err) {
                        console.error(err.message);
                    }
                    memDebug[name] = size;
                }
            }
            obj = process.memoryUsage();
            for (name in obj) {
                if (obj.hasOwnProperty(name)) {
                    memDebug[name] = obj[name];
                }
            }
            this.logger.log('--MEMDEBUG-- ' + JSON.stringify(memDebug), 'debug', NAME);
        }
    },


    error: function(err) {
        err = err || new Error('Unknown error occurred');
        if (!err.code) {
            err.code = 500;
        }

        if (err.code === 404) {
            // FUTURE: [Issue 96] default Mojito 404 page
            this.logger.log(err, 'warn', NAME);
        } else {
            this.logger.log(err, 'error', NAME);
        }

        var out = '<html>' +
                '<body><h1>Error: ' + err.code + '</h1>' +
                // The following line that includes the error message has been
                // removed because the Paranoids don't want this data to be
                // revealed in production environments. Once the bug having
                // to do with different development environments has been
                // fixed, we will be able to conditionally display the error
                // details.

                //                "<p>" + err.message + "</p>" +
                '<p>Error details are not available.</p>' +

                '</body>' +
                '</html>';
        // TODO: [Issue 96] If YUI._mojito.DEBUG, add stack.

        this.done(out, {
            http: {
                code: err.code,
                reasonPhrase: err.reasonPhrase,
                headers: {
                    'content-type': 'text/html'
                }
            }
        });
    },


    _stringify: function(obj, name) {
        var str,
            i;

        // uncomment this to debug circular structures...
        // console.error('------------------------------- ' + name.join('.'));
        // ... then use techniques below to blacklist those members

        if ('YUI.process.mainModule.children' === name.join('.')) {
            return 'YUI.process.mainModule.children circular reference';
        }
        i = name.length - 1;
        if ('_handle'  === name[i]) { return name.join('.') + ' circular reference'; }
        if ('_handles' === name[i]) { return name.join('.') + ' circular reference'; }
        if ('_yuiaop'  === name[i]) { return name.join('.') + ' circular reference'; }
        if ('_yuievt'  === name[i]) { return name.join('.') + ' circular reference'; }
        if ('context'  === name[i]) { return name.join('.') + ' circular reference'; }
        if ('host'     === name[i]) { return name.join('.') + ' circular reference'; }

        try {
            str = JSON.stringify(obj);
        } catch (err) {
            if ('Converting circular structure to JSON' === err.message && 'object' === typeof obj) {
                str = '';
                for (i in obj) {
                    if (obj.hasOwnProperty(i) && 'object' === typeof obj[i]) {
                        str += this._stringify(obj[i], name.concat([i]));
                    }
                }
            } else {
                throw err;
            }
        }
        return str;
    },


    _readMeta: function(meta) {
        if (!meta || !meta.http) { return; }

        var header;
        for (header in meta.http.headers) {
            if (meta.http.headers.hasOwnProperty(header)) {
                this.headers[header] = meta.http.headers[header];
            }
        }

        this.statusCode = meta.http.code;
        this.reasonPhrase = meta.http.reasonPhrase;
    },


    _writeHeaders: function() {
        if (!this.headersSent) {
<commit_msg>use util.inspect() instead of custom _stringify()<commit_after>/*
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */


/*jslint anon:true, sloppy:true, nomen:true*/


/**
 * This is an object used as the single pathway for data to leave a mojit
 * action execution. It is used as a component of the ActionContext object,
 * which uses it to call <em>done</em> and <em>flush</em> in order to complete.
 *
 * There are two versions of this object, one for the client, and one for the
 * server. This is the server version, which is more complex than the client
 * version.
 *
 * @class OutputHandler
 * @param {Object} req The Request object.
 * @param {Object} res The Response object.
 * @param {Function} next The next function, which should be invokable.
 * @constructor
 */
var NAME = 'OutputHandler.server',
    libutil = require('util'),
    OutputHandler = function(req, res, next) {
        this.req = req;
        this.res = res;
        this.next = next;
        this.headers = {};
    };


OutputHandler.prototype = {

    setLogger: function(logger) {
        this.logger = logger;
    },

    setMemoryDebugObjects: function(objs) {
        this.memDebugObjects = objs;
    },


    flush: function(data, meta) {
        this._readMeta(meta);
        this._writeHeaders();
        this.res.write(data);
    },


    done: function(data, meta) {
        var name,
            obj,
            size,
            memDebug = {};

        this.logger.log('done', 'info', NAME);
        this._readMeta(meta);
        this._writeHeaders();
        if (!data ||
                (typeof data !== 'string' && Object.keys(data).length === 0)) {
            data = '';
        }
        this.res.end(data);
        this.logger.log('END', 'mojito', NAME);

        // special memory debugging
        if (this.memDebugObjects) {
            for (name in this.memDebugObjects) {
                if (this.memDebugObjects.hasOwnProperty(name)) {
                    obj = this.memDebugObjects[name];
                    try {
                        size = libutil.inspect(obj).length;
                    } catch (err) {
                        console.error(err.message);
                    }
                    memDebug[name] = size;
                }
            }
            obj = process.memoryUsage();
            for (name in obj) {
                if (obj.hasOwnProperty(name)) {
                    memDebug[name] = obj[name];
                }
            }
            this.logger.log('--MEMDEBUG-- ' + JSON.stringify(memDebug), 'debug', NAME);
        }
    },


    error: function(err) {
        err = err || new Error('Unknown error occurred');
        if (!err.code) {
            err.code = 500;
        }

        if (err.code === 404) {
            // FUTURE: [Issue 96] default Mojito 404 page
            this.logger.log(err, 'warn', NAME);
        } else {
            this.logger.log(err, 'error', NAME);
        }

        var out = '<html>' +
                '<body><h1>Error: ' + err.code + '</h1>' +
                // The following line that includes the error message has been
                // removed because the Paranoids don't want this data to be
                // revealed in production environments. Once the bug having
                // to do with different development environments has been
                // fixed, we will be able to conditionally display the error
                // details.

                //                "<p>" + err.message + "</p>" +
                '<p>Error details are not available.</p>' +

                '</body>' +
                '</html>';
        // TODO: [Issue 96] If YUI._mojito.DEBUG, add stack.

        this.done(out, {
            http: {
                code: err.code,
                reasonPhrase: err.reasonPhrase,
                headers: {
                    'content-type': 'text/html'
                }
            }
        });
    },


    _readMeta: function(meta) {
        if (!meta || !meta.http) { return; }

        var header;
        for (header in meta.http.headers) {
            if (meta.http.headers.hasOwnProperty(header)) {
                this.headers[header] = meta.http.headers[header];
            }
        }

        this.statusCode = meta.http.code;
        this.reasonPhrase = meta.http.reasonPhrase;
    },


    _writeHeaders: function() {
        if (!this.headersSent) {

{'fraction_non_alphanumeric': 0.1311491068869997, 'fraction_numerical': 0.004680485242143471, 'mean_word_length': 1.552413456850317, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3074640', 'n_tokens_mistral': 2980, 'n_tokens_neox': 2800, 'n_words': 797}