samchon / tgrid

TypeScript RPC (Remote Procedure Call) for WebSocket and Worker protocols
https://tgrid.com/
MIT License
141 stars 19 forks source link

Protocol - Web Socket #5

Closed samchon closed 5 years ago

samchon commented 5 years ago

Outline

TGrid will support the Web Socket protocol.

The Web Socket related components would be implemented by extending and utilizing the Communicator and Driver classes.

Components

WebServer

namespace tgrid.protocols.web
{
    export class WebServer
    {
        /* ----------------------------------------------------------------
            CONSTRUCTORS
        ---------------------------------------------------------------- */
        /**
         * Default Constructor for the `ws` server.
         * 
         * Create an websocket server (`ws://`).
         */
        public constructor();

        /**
         * Initializer Constructor for the `wss` server.
         * 
         * Create a secured websocket server (`wss://`).
         * 
         * @param key Key string.
         * @param cert Certification string.
         */
        public constructor(key: string, cert: string);

        /* ----------------------------------------------------------------
            OPERATIONS
        ---------------------------------------------------------------- */
        /**
         * Open server.
         * 
         * @param port Port number to listen.
         * @param cb Callback function whenever client connects.
         */
        public open<Ret>(port: number, cb: (acceptor: WebAcceptor) => Ret): Promise<void>;

        /**
         * Close server.
         */
        public close(): Promise<void>;

        /**
         * Current state.
         */
        public get state(): WebServer.State;
    }

    export namespace WebServer
    {
        export const enum State 
        { 
            NONE = -1, 
            OPENING, 
            OPEN, 
            CLOSING, 
            CLOSED 
        }
    }
}

WebAcceptor

namespace tgrid.protocols.web
{
    export class WebAcceptor 
        extends basic.CommunicatorBase
    {
        /* ----------------------------------------------------------------
            CONSTRUCTORS
        ---------------------------------------------------------------- */
        /**
         * Hidden Constructor.
         * 
         * You can't create WebAcceptor by yourself. It would be created only by the WebServer.
         */
        private constructor(request);

        /**
         * Close connection.
         */
        public close(): Promise<void>;

        /* ----------------------------------------------------------------
            HANDSHAKES
        ---------------------------------------------------------------- */
        /**
         * Accept connection.
         * 
         * @param protocol Protocol you want to specialize.
         * @param allowOrigin If you want to restrict connection origin, then specialize it.
         * @param cookies Cookies to make client storing it.
         */
        public accept(protocol?: string, allowOrigin?: string, cookies?: ICookie[]): Promise<void>;

        /**
         * Reject connection.
         * 
         * @param status Status code.
         * @param reason Detailed reason to reject.
         * @param extraHeaders Extra heaaders if required.
         */
        public reject(status?: number, reason?: string, extraHeaders?: object): Promise<void>;

        /**
         * Start listening.
         * 
         * Start listening data (function requests) from the remote client.
         * 
         * @param provider An object to provide functions for the remote client.
         */
        public listen<Provider extends object>
            (provider: Provider): Promise<void>;

        /* ----------------------------------------------------------------
            ACCESSORS
        ---------------------------------------------------------------- */
        /**
         * Get driver for remote controller.
         *
         * @return A driver for the remote Controller.
         */
        public getDriver<Controller extends object>(): Driver<Controller>;

        /**
         * Join connection.
         */
        public join(): Promise<void>;

        //----
        // PROPERTIES
        //----
        public get state(): State;
        public get secured(): boolean;

        public get path(): string;
        public get protocol(): string;
        public get extensions(): string;
    }

    export namespace WebAcceptor
    {
        export interface ICookie { ... };

        export const enum State { ... }
    }
}

WebConnector

namespace tgrid.protocols.web
{
    export class WebConnector<Provider extends object>
        extends basic.CommunicatorBase<Provider>
    {
        /* ----------------------------------------------------------------
            CONSTRUCTORS
        ---------------------------------------------------------------- */
        /**
         * Initializer Constructor.
         * 
         * @param provider A provider for server.
         */
        public constructor(provier?: Provider);

        /**
         * Connect to remote web socket server.
         *
         * @param url URL address to connect.
         * @param protocols Candidate protocols to specialize.
         */
        public connect(url: string, protocols?: string | string[]): Promise<void>;

        /**
         * Close connection.
         * 
         * @param code Closing code.
         * @param reason Reason why.
         */
        public close(code?: number, reason?: string): Promise<void>;

        /* ----------------------------------------------------------------
            ACCCSSORS
        ---------------------------------------------------------------- */
        /**
         * Get driver for remote controller.
         *
         * @return A driver for the remote Controller.
         */
        public getDriver<Controller extends object>(): Driver<Controller>;

        /**
         * Wait server to provide.
         * 
         * Wait server to specify its `Provider`.
         */
        public wait(): Promise<void>;

        /**
         * Join connection.
         */
        public join(): Promise<void>;

        //----
        // PROPERTIES
        //----
        public get state(): WebConnector.State;
        public get url(): string;
        public get protocol(): string;
        public get extensions(): string;
    }

    export namespace WebConnector
    {
        export const enum State
        {
            NONE = -1,
            CONNECTING,
            OPEN,
            CLOSING,
            CLOSED
        }
    }
}

Sample Code

server.ts

import * as std from "tstl";
import * as tgrid from "tgrid";

import { Calculator } from ".internal/Calculator";

async function main(): Promise<void>
{
    let server: WebServer = new WebServer();
    await server.open(PORT, async acceptor =>
    {
        await acceptor.accept(); // ALLOW CONNECTION
        await acceptor.listen(/calculator/.test(acceptor.path)
            ? new Calculator()
            : new std.Vector<number>()); // SET LISTENER
    });
}

client-vector.ts

import { Vector } from "tstl/container";
import { WebConnector } from "tgrid/protocols/web";
import { Driver } from "tgrid/basic";

type IVector<T> = Pick<Vector<T>, "size" | "at" | "push_back">;

async function main(): Promise<void>
{
    let connector: WebConnector = new WebConnector();
    await connector.connect("ws://127.0.0.1:10100");

    let vec: Driver<IVector<number>> = connector.getDriver<IVector<number>>();
    for (let i: number = 0; i < 5; ++i)
        await vec.push_back(i);

    console.log("size:", await vec.size());
    for (let i: number = 0; i < await vec.size(); ++i)
        console.log("  element:", await vec.at(i));
}
main();

client-calculator.ts

import { WebConnector } from "tgrid/protocols/web";
import { Driver } from "tgrid/basic";
import { ICalculator } from "./internal/Calculator";

async function main(): Promise<void>
{
    //----
    // PREPARES
    //----
    // DO CONNECT
    let connector: WebConnector = new WebConnector();
    await connector.connect("ws://127.0.0.1:10102");

    // GET DRIVER
    let calc: Driver<ICalculator> = connector.getDriver<ICalculator>();

    //----
    // CALL FUNCTIONS
    //----
    // FUNCTIONS IN THE ROOT SCOPE
    console.log("1 + 6 =", await calc.plus(1, 6));
    console.log("7 * 2 =", await calc.multiplies(7, 2));

    // FUNCTIONS IN AN OBJECT (SCIENTIFIC)
    console.log("3 ^ 4 =", await calc.scientific.pow(3, 4));
    console.log("log (2, 32) =", await calc.scientific.log(2, 32));

    try
    {
        // TO CATCH EXCEPTION IS STILL POSSIBLE
        await calc.scientific.sqrt(-4);
    }
    catch (err)
    {
        console.log("SQRT (-4) -> Error:", err.message);
    }

    // FUNCTIONS IN AN OBJECT (STATISTICS)
    console.log("Mean (1, 2, 3, 4) =", await calc.statistics.mean(1, 2, 3, 4));
    console.log("Stdev. (1, 2, 3, 4) =", await calc.statistics.stdev(1, 2, 3, 4));

    //----
    // TERMINATE
    //----
    await connector.close();
}
main();

internal/Calculator.ts

/* ----------------------------------------------------------------
    INTERFACES
---------------------------------------------------------------- */
export interface ICalculator
{
    scientific: IScientific;
    statistics: IStatistics;

    plus(x: number, y: number): number;
    minus(x: number, y: number): number;
    multiplies(x: number, y: number): number;
    divides(x: number, y: number): number;
}

export interface IScientific
{
    pow(x: number, y: number): number;
    sqrt(x: number): number;
    log(x: number, y: number): number;
}

export interface IStatistics
{
    mean(...elems: number[]): number;
    stdev(...elems: number[]): number;
}

/* ----------------------------------------------------------------
    CLASSES
---------------------------------------------------------------- */
export class Calculator implements ICalculator
{
    public scientific = new Scientific();
    public statistics = new Statistics();

    public plus(x: number, y: number): number
    {
        return x + y;
    }
    public minus(x: number, y: number): number
    {
        return x - y;
    }

    public multiplies(x: number, y: number): number
    {
        return x * y;
    }
    public divides(x: number, y: number): number
    {
        if (y === 0)
            throw new Error("Divided by zero.");
        return x / y;
    }
}

class Scientific implements IScientific
{
    public pow(x: number, y: number): number
    {
        return Math.pow(x, y);
    }

    public log(x: number, y: number): number
    {
        if (x < 0 || y < 0)
            throw new Error("Negative value on log.");
        return Math.log(y) / Math.log(x);
    }

    public sqrt(x: number): number
    {
        if (x < 0)
            throw new Error("Negative value on sqaure.");
        return Math.sqrt(x);
    }
}

class Statistics implements IStatistics
{
    public mean(...elems: number[]): number
    {
        let ret: number = 0;
        for (let val of elems)
            ret += val;
        return ret / elems.length;
    }

    public stdev(...elems: number[]): number
    {
        let mean: number = this.mean(...elems);
        let ret: number = 0;

        for (let val of elems)
            ret += Math.pow(val - mean, 2);

        return Math.sqrt(ret / elems.length);
    }
}