UNPKG

tgrid

Version:

Grid Computing Framework for TypeScript

269 lines (247 loc) 8.35 kB
import type http from "http"; import type https from "https"; import type net from "net"; import { is_node } from "tstl"; import type WebSocket from "ws"; import { NodeModule } from "../../utils/internal/NodeModule"; import { IServer } from "../internal/IServer"; import { WebSocketAcceptor } from "./WebSocketAcceptor"; /** * Web Socket Server. * * - available only in the NodeJS. * * The `WebSocketServer` is a class who can open an websocket server. Clients * connecting to the `WebSocketServer` would communicate with this websocket server * through {@link WebSocketAcceptor} instances with RPC (Remote Procedure Call) * concept. * * To open the websocket server, call the {@link open} method with your callback * function which would be called whenever a {@link WebSocketAcceptor} has been * newly created by a new client's connection. * * Also, when declaring this {@link WebSocketServer} type, you have to define three * generic arguments; `Header`, `Provider` and `Remote`. Those generic arguments * would be propagated to the {@link WebSocketAcceptor}, so that * {@link WebSocketAcceptor} would have the same generic arguments, too. * * For reference, the first `Header` type represents an initial data from the * remote client after the connection. I recommend utilize it as an activation tool * for security enhancement. The second generic argument `Provider` represents a * provider from server to client, and the other `Remote` means a provider from the * remote client to server. * * @template Header Type of header containing initialization data like activation. * @template Provider Type of features provided for the remote client. * @template Remote Type of features provided by remote client. * @author Jeongho Nam - https://github.com/samchon */ export class WebSocketServer< Header, Provider extends object | null, Remote extends object | null, > implements IServer<WebSocketServer.State> { /** * @hidden */ private state_: WebSocketServer.State; /** * @hidden */ private options_: https.ServerOptions | null; /** * @hidden */ private server_: http.Server | https.Server | null; /** * @hidden */ private protocol_: WebSocket.Server | null; /* ---------------------------------------------------------------- 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); public constructor(key?: string, cert?: string) { if (is_node() === false) throw new Error( "Error on WebSocketServer.constructor(): only available in NodeJS.", ); // PREPARE SERVER INSTANCE this.options_ = !!key && !!cert ? { key, cert } : null; // INITIALIZE STATUS & PROTOCOL this.state_ = WebSocketServer.State.NONE; this.server_ = null; this.protocol_ = null; } /** * Open websocket server. * * Open a server through the web-socket protocol, with its *port* number and *handler* * function determining whether to accept the client's connection or not. After the server has * been opened, clients can connect to that websocket server by using the {@link WebSocketConnector} * class. * * When implementing the *handler* function with the {@link WebSocketAcceptor} instance, calls the * {@link WebSocketAcceptor.accept} method if you want to accept the new client's connection. * Otherwise you don't want to accept the client and reject its connection, just calls the * {@link WebSocketAcceptor.reject} instead. * * @param port Port number to listen. * @param handler Callback function for client connection. */ public async open( port: number, handler: ( acceptor: WebSocketAcceptor<Header, Provider, Remote>, ) => Promise<void>, ): Promise<void> { //---- // PRELIMINARIES //---- // POSSIBLE TO OPEN? if (this.state_ === WebSocketServer.State.OPEN) throw new Error( "Error on WebSocketServer.open(): it has already been opened.", ); else if (this.state_ === WebSocketServer.State.OPENING) throw new Error( "Error on WebSocketServer.open(): it's on opening, wait for a second.", ); else if (this.state_ === WebSocketServer.State.CLOSING) throw new Error("Error on WebSocketServer.open(): it's on closing."); // DO OPEN else if ( this.server_ === null || this.state_ === WebSocketServer.State.CLOSED ) this.server_ = this.options_ !== null ? (await NodeModule.https.get()).createServer(this.options_!) : (await NodeModule.http.get()).createServer(); this.protocol_ = new (await NodeModule.ws.get()).default.Server({ noServer: true, }); // SET STATE this.state_ = WebSocketServer.State.OPENING; //---- // OPEN SERVER //---- // PROTOCOL - ADAPTOR & ACCEPTOR this.server_.on( "upgrade", ( request: http.IncomingMessage, netSocket: net.Socket, header: Buffer, ) => { this.protocol_!.handleUpgrade(request, netSocket, header, (socket) => WebSocketAcceptor.upgrade(request, socket, handler), ); }, ); // FINALIZATION await WebSocketServer._Open( this.server_, port, (state) => (this.state_ = state), ); } /** * Close server. * * Close all connections between its remote clients ({@link WebSocketConnector}s). * * It destroys all RFCs (remote function calls) between this server and remote clients * (through `Driver<Controller>`) that are not returned (completed) yet. The destruction * causes all incomplete RFCs to throw exceptions. */ public async close(): Promise<void> { // VALIDATION if (this.state_ !== WebSocketServer.State.OPEN) throw new Error( "Error on WebSocketServer.close(): server is not opened.", ); // DO CLOSE this.state_ = WebSocketServer.State.CLOSING; await this._Close(); this.state_ = WebSocketServer.State.CLOSED; } /** * @hidden */ private static _Open( server: http.Server | https.Server, port: number, setState: (state: WebSocketServer.State) => void, ): Promise<void> { return new Promise((resolve, reject) => { // PREPARE RETURNS server.on("listening", () => { setState(WebSocketServer.State.OPEN); server.on("error", () => {}); resolve(); }); server.on("error", (error) => { setState(WebSocketServer.State.NONE); reject(error); }); // DO OPEN - START PROVIDE server.listen(port); }); } /** * @hidden */ private _Close(): Promise<void> { return new Promise((resolve) => { this.protocol_!.close(() => { this.server_!.close(() => { resolve(); }); }); }); } /* ---------------------------------------------------------------- ACCESSORS ---------------------------------------------------------------- */ /** * Get server state. * * Get current state of the websocket server. * * List of values are such like below: * * - `NONE`: The `{@link WebSocketServer} instance is newly created, but did nothing yet. * - `OPENING`: The {@link WebSocketServer.open} method is on running. * - `OPEN`: The websocket server is online. * - `CLOSING`: The {@link WebSocketServer.close} method is on running. * - `CLOSED`: The websocket server is offline. */ public get state(): WebSocketServer.State { return this.state_; } } /** * */ export namespace WebSocketServer { /** * Current state of the {@link WebSocketServer}. */ export import State = IServer.State; }