Skip to content

Latest commit

 

History

History
246 lines (194 loc) · 8.4 KB

http-context.md

File metadata and controls

246 lines (194 loc) · 8.4 KB

HTTP

Overview

import FS from "fs";
import OS from "os";
import HTTP from "http";
import Process from "process";
import Cluster from "cluster";

import Threads from "worker_threads"

import { Readable } from "stream";

type Generic = any;
type Asynchronous = (route: URL, request: HTTP.IncomingMessage, response: Outbound) => Promise<Generic>;
type Request = HTTP.IncomingMessage;
type Response = HTTP.ServerResponse;

type Listener = HTTP.RequestListener;

export interface Inbound extends HTTP.ClientRequest {}

export interface Outbound extends Response, HTTP.OutgoingMessage {}

/***
 * A mapping of header names to string values.
 *
 * Keys should be considered case insensitive, even if this is not enforced by a
 * particular implementation.
 *
 */
export interface Headers {
    [header: string]: string;
}

/***
 * Represents an HTTP message with headers and an optional static or streaming.
 *
 * @param headers { Headers }
 * @param body {ArrayBuffer | ArrayBufferView | string | Uint8Array | Readable | ReadableStream}
 *
 */
export interface Message {
    headers: Headers;
    body?: ArrayBuffer | ArrayBufferView | string | Uint8Array | FS.ReadStream | ReadableStream | Buffer | Readable;
}

/***
 * A mapping of query parameter names to strings or arrays of strings, with the
 * second being used when a parameter contains a list of values.
 *
 * Value is to default to `null` when `Parameters` shape isn't required.
 *
 */
export interface Parameters {
    [parameter: string]: string | Array<string> | null;
}

export interface Endpoint {
    protocol: string;
    hostname: string;
    port?: number;
    path: string;
    query?: Parameters;
}

class Handler extends HTTP.Server {
    /*** Can be used for debugging child processes (Actual HTTP Server(s) responding to /api-endpoint requests) */
    static Base: Handler = new Handler();

    initialize = (options: Listener) => HTTP.createServer(options);

    constructor() {
        super();
    }

    static async handle(request: Request, response: Outbound, url: URL, handler?: Asynchronous) {
        response.writeHead(200);

        /// console.log(request, response, url, handler ?? null);

        response.end((handler) ? await handler(url, request, response) : () => null);
    }

    async default(request: Request, response: Outbound, url: URL, handler?: Asynchronous) {
        response.writeHead(200);

        /// console.log(request, response, url, handler ?? null);

        response.end((handler) ? await handler(url, request, response) : () => null);
    }

    async get(request: Request, response: Outbound, url: URL, handler?: Asynchronous) {
        (request.method !== "GET") && response.writeHead(405);
        (request.method !== "GET") && response.end();

        if (request.method === "GET") {
            await Handler.handle(request, response, url, handler)
        }
    }

    async post(request: Request, response: Outbound, url: URL, handler?: Asynchronous) {
        (request.method !== "POST") && response.writeHead(405);
        (request.method !== "POST") && response.end();

        if (request.method === "POST") {
            await Handler.handle(request, response, url, handler)
        }
    }

    async update(request: Request, response: Outbound, url: URL, handler?: Asynchronous) {
        (request.method !== "UPDATE") && response.writeHead(405);
        (request.method !== "UPDATE") && response.end();

        if (request.method === "UPDATE") {
            await Handler.handle(request, response, url, handler)
        }
    }

    async delete(request: Request, response: Outbound, url: URL, handler?: Asynchronous) {
        (request.method !== "DELETE") && response.writeHead(405);
        (request.method !== "DELETE") && response.end();

        if (request.method === "DELETE") {
            await Handler.handle(request, response, url, handler)
        }
    }
}

const Server = new Handler();

/// After ... Some confusion, it was found that Node.js does not allow TCP-sharing between the Master --> Child processes
///
/// Academically, I believe parent-child process IPC can be shared; certainly, however, port access permissions
/// ... *are not shared amongst all processes*. Which makes sense due to security concerns, potential DDoS if `ioperm`
/// ... bits are enabled to both/multiple processes.
///
/// Note, and I believe this holds true on most unix-based systems, multi-threaded-capable application worker's
/// ... must only make asynchronous syscalls. But node.js's runtime is inherently asynchronous... Lucky catch,
/// ... especially when the context comes down to clustering intra-system...ly.

if (Cluster.isPrimary && Threads.isMainThread) { /// Parent Process
    console.log("Main Event Loop", Process.pid);

    OS.cpus().forEach(() => {
        Cluster.fork();
        /// --> new Worker(import.meta.url.replace("file://", ""));
    });

    // --> Track Total HTTP Requests
    let $ = 0;
    // <-- Count HTTP Requests
    const receiver = (message: Generic) => {
        $ += (message?.cmd && message?.cmd === "reception") ? 1 : 0;

        (message?.cmd && message?.cmd === "reception") && console.log( "Request Closure" + " " + "(" + $ + ")" + "\t", message?.id[0], message?.id[1] );

        /// ... Very interesting Effects on Cluster := `for (let n = 0; n < 1e10; n++)`
        /// --> Blocks given that communication through process.stdout requires a momentarily blocking state
    }

    Cluster.on("exit", (worker, code, signal) => {
        if (signal) {
            console.log("Worker (SIGKILL)" + ":", worker);
        } else if (code) {
            console.log("Worker (Error)" + ":", code);
        } else {
            console.log("Successful Termination", worker);
        }

        console.log("Worker" + ":", "(" + worker + ")", "Code" + ":", "(" + code + ")", "Signal" + ":", "(" + signal + ")");
    });

    for (const $ in Cluster.workers) {
        // @ts-ignore
        Cluster?.workers[$].on("message", receiver);
    }

    console.log( "  - HTTP Endpoint: http://127.0.0.1:8080/api-endpoint" );
} else { /// Child Process(es)
    console.log("Worker Process" + ":", Process.pid);

    Server.initialize( async (request: Request, response: Response) => {
        console.log( "Request Initialization", "\t", Process.ppid, Process.pid);
        /***
         * Force a Heavy Computation
         * ---
         *
         * @summary
         *
         * In retrospect, forcing a synchronous event in replacement of an actual, CPU-expensive task wasn't a great example.
         * Because Node.js is single-threaded, calling `Wait` simply locks up the thread, rather than allow other processes
         * to progress any mutexes further.
         *
         * However, I won't be spending additional efforts writing anything intensive as the following example does
         * successfully show that all process(es) do resolve HTTP requests through concurrency.
         *
         * Apache Load Testing (Default on MacOS)
         * $ ab -n 1000 -c 100 http://localhost:8080/api-endpoint
         *
         * Via NPM Package: "loadtest" (Seems ... oddly similar)
         * $ npm install --global loadtest
         * $ loadtest http://localhost:8080/api-endpoint -n 1000 -c 100
         *
         * @note Execution via Windows is unknown, and will not be supported regardless
         *
         */

        /// await Wait(10000);
        /// function Wait(time: number) {
        ///     return new Promise((resolve) => {
        ///         setTimeout(resolve.bind(null), time)
        ///     })
        /// }

        // ... Well, here would be a decent example ... but boring
        for (let n = 0; n < 1e7; n++) { /*** */ }

        const Implementation = {
            Default: Server.get
        };

        const Options = new URL( String( request.url ), "http://127.0.0.1/" );

        /// --> For Use in any Real Context other than Research & Testing := `console.log( "Request Data" + ":", { ... Options, ... { worker: Process.pid } } );`

        /// console.debug( "[Debug]", (Utility.inspect( Server )) );

        (Options.pathname === "/api-endpoint") && await Implementation.Default( request, response, Options);

        (Options.pathname === "/api-endpoint") || response.writeHead( 404 );
        (Options.pathname === "/api-endpoint") || response.end();

        // @ts-ignore
        Process?.send({ cmd: "reception", id: [Process.ppid, Process.pid] });

        response.end();
    } ).listen( 8080, () => {
        Server.timeout = 1000 * 15;
    } );
}

export {}