Home Identifier Source Repository

src/services/$Request.js

/**
 * @module $Request.js
 * @author Joe Groseclose <@benderTheCrime>
 * @date 8/16/2015
 */

// System Modules
import url from                     'url';
import { Form } from                'multiparty';

// Angie Modules
import { default as $Routes } from  '../factories/$RouteProvider';
import * as $Responses from         './$Response';
import $Util, { $StringUtil } from  '../util/Util';

/**
 * @desc The $Request class processes all of the incoming Angie requests. It
 * can be required using a module import, but probably should not be unless it
 * is being subclassed for a dependency package. It can also be used as an
 * injected provider using `$request`.
 * @since 0.0.1
 * @access private
 */
class $Request {
    constructor(request) {
        let $routes;

        $Util._extend(this, request);
        this.$$request = request;

        // Define URI
        this.url = request.url;
        this.path = url.parse(this.url).pathname;

        // Parse query params out of the url
        this.query = url.parse(this.url, true).query;

        $routes = $Routes.fetch();

        // Declare the routes on the local request object
        this.routes = $routes.routes;
        this.otherwise = $routes.otherwise;
    }

    /**
     * @desc Returns a rerouted request which will perform a site redirect on
     * the front end.
     * @since 0.4.0
     * @param {string} path The relative or absolute path to which the Response
     * is redirected.
     * @access private
     */
    $redirect(path) {
        return new $Responses.RedirectResponse(path).head().writeSync();
    }

    /**
     * @desc Performs the routing of all Angie requests
     * @since 0.4.0
     * @access private
     */
    $$route() {

        // Check against all of the RegExp routes in Reverse
        let regExpRoutes = Object.keys(this.routes.regExp || {}).reverse();

        for (let i = 0; i < regExpRoutes.length; ++i) {

            // Slice characters we do not need to instantiate a new RegExp
            let regExpRoute =
                $StringUtil.removeTrailingLeadingSlashes(regExpRoutes[ i ]),

                // Cast the string key of the routes.regExp object as a
                // RegExp obj
                pattern = new RegExp(regExpRoute);

            if (pattern.test(this.path)) {
                this.route = this.routes.regExp[ regExpRoutes[ i ] ];

                // Hooray, we've set our route, now we need to do some additional
                // param parsing
                $Util._extend(
                    this.query,
                    $Routes.$$parseURLParams(pattern, this.path)
                );

                break;
            }
        }

        // Check for a matching string path
        if (!this.route && this.routes[ this.path ]) {
            this.route = this.routes[ this.path ];
        }

        // Route the request based on whether the route exists and what the
        // route states its response should contain.
        let ResponseType;
        try {
            if (this.route) {
                ResponseType = 'ControllerTemplate';
                if (this.route.templatePath) {
                    ResponseType += 'Path';
                }
            } else if (
                $Responses.AssetResponse.$isRoutedAssetResourceResponse(
                    this.path
                )
            ) {
                ResponseType = 'Asset';
            } else if (this.otherwise) {
                return new $Responses.RedirectResponse().head().writeSync();
            } else {
                ResponseType = 'Unknown';
            }

            // Perform the specified response type
            return new $Responses[ `${ResponseType}Response` ]().head().write();
        } catch(e) {

            // Throw an error response if no other response type was specified
            return new $Responses.ErrorResponse(e).head().write();
        }
    }

    $$data() {
        let me = this,
            request = this.$$request,
            proms = [],
            prom;
        delete this.$$request;
        request.body = '';
        request.formData = {};

        prom = new Promise(function(resolve) {
            let body = '';
            request.on('data', function(d) {
                body += d;
                if (body.length > 1E6) {
                    request.connection.destroy();
                    throw new Error();
                }
            });
            request.on('end', function() {
                me.body = request.body = body;
                resolve();
            });
        });

        proms.push(prom);

        prom = new Promise(function(resolve) {
            try {
                new Form().parse(request, function(e, ...data) {
                    resolve(data);
                });
            } catch(e) {
                resolve([]);
            }
        })

        proms.push(prom);
        prom.then(function() {
            let rawData = arguments[0][0] || {},
                files = arguments[0][1] || {},
                formData = {};
            for (let field in rawData) {
                formData[ field ] = typeof rawData[ field ] === 'object' ?
                    rawData[ field ][0] : rawData[ field ];
            }
            me.formData = request.formData = formData;
            me.files = request.files = files;
        });

        return Promise.all(proms);
    }
}

export default $Request;