0.2.21 • Published 3 years ago

dream_rpc v0.2.21

Weekly downloads
-
License
ISC
Repository
-
Last release
3 years ago

What is DreamRpc?

DreamRpc is a general purpose RPC framework that primary designed for easy way to communicate and transfer realtime data across most JavaScript/TypeScript runtimes such as Web browser, Node.JS, Deno and React native like mobile development frameworks, Html5 games etc.

you can use this framework for building realtime data processing applications, Html5 based multiplayer game servers, micro services etc.

Why DreamRpc?

Advantages:

  • Written in TypeScript, object oriented design (also you can use JavaScript)
  • Bi-Directional communication between Rpc server and Rpc client
  • No magic, simple, lightweight and easy to use
  • No callback, no string event handler like "event.on("xxx", callback), event.on("yyy", callback)", all api is designed for Type Safe.

Disadvantages:

  • New so maybe has unknown (or known) bugs
  • under development progress so maybe change API in future
  • Only available on TypeScript/JavaScript at this moment

What languages are available?

  • TypeScript/JavaScript runtime (node.js, web browser, mobile devices, java script game engines)

but we have plan for implement other program languages/platforms,

here is the list of languages/platforms we are planned and future will be implemented:

  • Vala programming language server side (for making high performance game server purpose)
  • PHP server side
  • Dart server side and flutter
  • Kotlin for JVM platforms
  • .Net Core platform.

Quick start in five minutes

first of all, take a look the result of below example

Simple example

in this quick start examples we will make a simple broadcast application

development requirements:

  • Node.js of the latest version
  • TypeScript compiler (this is optional if you want to use JavaScript but we highly recommend using TypeScript for you)
  • Web browser

####feature list should be implemented:

  • some client sends a message to all clients
  • all connected clients will receive the broadcast message.

Write server side code

  1. prepare your package.json and tsconfig.json with following the command (or any methods)
  • create a directory with mkdir broadcast_server and open the dir with cd broadcast_server
  • create a packages.json with npm init (default contents of package.json is enough)
  • create a tsconfig.json with tsc --init (if you have no installed TypeScript compiler then you can install it with npm install typescript --global) the content of tsconfig file looks like this:
    {
     "compilerOptions": {
       "target": "EsNext",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
       "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
       "outDir": "./bin",                        /* Redirect output structure to the directory. */
       "rootDir": "./src",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
       "strict": true,                           /* Enable all strict type-checking options. */
       "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
       "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
       "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
       "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
       "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
       "skipLibCheck": true,                     /* Skip type checking of declaration files. */
       "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
     }
    }
 - install necessary dependencies with npm (or yarn if you like) on terminal:
   - `npm install @types/node --save-dev` (for code editor auto complete node.js api)
   - `npm install dream_rpc --save` (core dependencies for this framework)
   - `npm install dream_rpc_node_websocket --save` (for websocket host support)
   - `npm install ws --save` and `npm install @types/ws --save-dev` (only required for websocket host support)
 - create a src directory for our source code with `mkdir src`
 - create a TypeScript file named with `Shared.ts` and put these code into it

import { IStatefulClientInterface } from "dream_rpc"; / Server side rpc methods definition / export interface IBroadcastServerInterface { / Return value should be return a promise object IF YOU WANT TO two or more parameters then should wrap these parameters at an object like "createUser(args: {name: string, email: string}): Promise" instead of createUser(name: string, email: string): Promise / sendBroadcast(args: { message: string, sender: string }): Promise } /** Client side rpc methods definition */ export interface IBroadcastClientInterface extends IStatefulClientInterface { onBroadcastReceived(args: { message: string, sender: string }): Promise }

above code is a rpc method definition of server and client, we will implement these definitions on our rpc server and rpc client later
- create a TypeScript file named `MyRpcServer.ts` and put below code into it.

import { RpcClient, RpcServer, State, StateManagerInMemory } from "dream_rpc"; import { WebSocketServerHost } from "dream_rpc_node_websocket" import { IBroadcastClientInterface, IBroadcastServerInterface } from "./Shared"; import * as Http from "http" import WebSocket from "ws";

/ State object of rpc client on server / class MyBroadcastClientState extends State {} / Server side rpc methods implementation / class MyBroadcastServerImplementation implements IBroadcastServerInterface { /Non rpc method object names are should be start with "_", for example: "_client", "_xxNotRpcObject", for more details: please visit github (or gitee) documentation.*/ public constructor(private _client: MyBroadcastClient) {} public async sendBroadcast(args: { message: string; sender: string; }): Promise { console.log(A broadcast message received by [${args.sender}] that content of [${args.message}]) /get all connected clients on this server and send the received message to each / this._client.server.clients.forEach((client) => { (client as MyBroadcastClient).bridge.remoteInterface.onBroadcastReceived({ message: args.message, sender: args.sender }) }) } } class MyBroadcastClient extends RpcClient<IBroadcastServerInterface, IBroadcastClientInterface, MyBroadcastClientState> { protected async createState(): Promise { return new MyBroadcastClientState() } protected async getLocalInterfaceImplementation(): Promise { return new MyBroadcastServerImplementation(this) } /** This is optional lifecycle method, don't write if you don't need / protected async onConnected(): Promise { await super.onConnected() //Must call super console.log("Client connected") } /** This is optional lifecycle method, don't write if you don't need / protected async onReady(): Promise { await super.onReady() //Must call super console.log("Connected client is now ready to use") } /** This is optional lifecycle method, don't write if you don't need / protected async onDisconnected(): Promise { await super.onDisconnected() //Must call super console.log("Client is disconnected") } } class MyBroadcastServer extends RpcServer<IBroadcastServerInterface, IBroadcastClientInterface, MyBroadcastClientState> { protected async createClientInstance(): Promise<RpcClient<IBroadcastServerInterface, IBroadcastClientInterface, MyBroadcastClientState>> { return new MyBroadcastClient() } /** This is optional lifecycle method, don't write if you don't need */ protected async onLaunch(): Promise { await super.onLaunch() //Must call super console.log("Rpc server is start: OK") } protected async onShutdown(): Promise { await super.onShutdown() //Must call super console.log("Rpc server is shutdown") } }

async function main() { const httpServer = Http.createServer((request, response) => { response.end("Please communicate with WebSocket protocol") }) httpServer.listen(8000)

   const myServer = new MyBroadcastServer(
       new WebSocketServerHost(async () => new WebSocket.Server({
           path: "/Broadcast",
           server: httpServer
       })),
       new StateManagerInMemory()
   )

   await myServer.prepareForLaunch() //Prepare for launch server, in this step, server is register internal callback for launch
   await myServer.launch() //start rpc server

} main()

now you can compile this code with `tsc` and start rpc server with `node ./bin/MyRpcServer.js`
#### Write client side code (Node.js as client)
- Create a TypeScript file in `src` directory named with `MyRpcClient.ts` and put following content:

import { IBroadcastClientInterface, IBroadcastServerInterface } from "./Shared"; import { IStateModule, RpcBridge, StateModuleInMemoryImplementation } from "dream_rpc"; import WebSocket from "ws"; import { WebSocketBridgeHost } from "dream_rpc_node_websocket";

class MyBroadcastClientImplementation implements IBroadcastClientInterface { / Related state management, please visit github (or gitee) documentation / stateModule: IStateModule = new StateModuleInMemoryImplementation() / Calling when server side occur events related this client. @param error error */ public async sendErrorMessage(error: string): Promise { console.error(error) }

   public async onBroadcastReceived(args: { message: string; sender: string; }): Promise<void> {
       console.log(`A message received by [${args.sender}] with the following content:`)
       console.log(args.message)
   }

}

async function main() { const client = new RpcBridge<IBroadcastClientInterface, IBroadcastServerInterface>( new WebSocketBridgeHost(async () => new WebSocket("ws://127.0.0.1:8000/Broadcast")), new MyBroadcastClientImplementation() ) await client.connect() await client.remoteInterface.sendBroadcast({ sender: "مۇختەرجان", message: "Hello there, I'm coming..." }) } main()

- compile with `tsc` and run with `node ./bin/MyRpcClient.js`
#### Write client code(Web browser version)
actually the client code can be run directly on web browser, but there some a little difference:
- For older version of browsers, we should use some techniques like babel for support ES6 features
- Web browsers are not support ES6 style module import/export statements, so we should change our code to browser compatible code, of course we can use some techniques like`webpack`.

this framework provided browser compatible versions of necessary libraries.

so where I find it? actually npm packages of this framework is contain browser compatible versions in its directory.
- Get necessary dependency files
- `node_modules/dream_rpc/bin/dream_rpc_browser.development.js` or `node_modules/dream_rpc/bin/dream_rpc_browser.production.js`
- `node_modules/dream_rpc_node_websocket/bin/dream_rpc.websocket_bridge_host.development.js` or `node_modules/dream_rpc_node_websocket/bin/dream_rpc.websocket_bridge_host.production.js`
- browser compatible version of the client code, for this, create a new html file under `src` folder named with `MyRpcClient.html` and put the following code:

Also you can get full code at: https://gitee.com/yeganaaa/dream-rpc-core-examples

Fundamentals

Architecture

Architecture

Lifecycle

Lifecycle

this framework consists some basic concepts that:

  • Rpc server, this is corresponds Http server in http frameworks but Rpc server is not stateless
  • Rpc client, this is corresponds Http client, but rpc client is not stateless
  • State management, maybe this is corresponding session in http servers but also has some difference, state management is responsible to save/restore user state when rpc client is connecting/disconnecting
  • Transport layer, Rpc server has no any transport functionality, so it is dependent on transport layer like Tcp socket, Web socket, Http long pooling etc.

Detailed explains

first of all, this framework is fully bi-directional (of course, this is optional if you don't need this). so we need to define a set of rpc methods on both server side and client side, we can directly call these rpc methods from anther way.

for example: we want to make a chat room application

on the server side, we will define some methods like "createRoom", "deleteRoom", "joinToRoom", "leaveFromRoom", "sendMessageToRoom"

client side we will need to define some methods like "onRoomCreated", "onRoomDeleted", "onUserJoin", "onUserLeave", "onMessageReceived" etc.

on the server side, we can directly call client side methods, and on the client side, directly call server side methods.

Rpc server

Rpc server is a high abstraction of socket server, it has its own lifecycle and management methods such as "start", "stop".

it's responsible to manage rpc client connections, for example: accept and create, hold rpc client instance, manage clients state through StateManager.

all rpc client instances are holds by rpc server, we can get all connected client instances using "someRpcServer.clients" property, and foreach them, call each client rpc methods.

Rpc client

rpc client is high abstraction of socket client, also rpc client has its own lifecycles and management methods, this is correspond a user, a client, rpc client holds a state object that save the client state, a bridge object consists server side rpc methods implementation object and client side rpc methods implementation delegate object, we can make actual rpc call through these implementation objects. they are will details on below example.

State management

State management is a concept that if a user disconnects from server then server will save the client state, if client is reconnect, server restore the client state into rpc client instance object. rpc client instance has a property called state, this state object is corresponds "session" in http server.

for example: we make a simple game, user has score in the game, if the user connection is lost and reconnect after 5 second, then server should be save and restore the score of the user.

State object of rpc client is can do this. we can save client temporary data to the state object in client instance like user has login or not.

Transport layer

both rpc server and rpc client is a high level abstraction of socket server and socket client, but they are no have actual communication functionality.

so we need a actual transport layer like TcpSocket, WebSocket or other

so we need to supply some actual transport layer to Rpc server and rpc client, maybe this transport layer is tcp socket or web socket or other.

at the moment, we have done some popular transport layers implementation, TcpSocket and WebSocket.

we can customize our own transport layers for security needs or other requirements

for example, we need to encrypt and decrypt the data before/after sending or compress and decompress the data before/after sending. also for these requirements, we can make our custom transport layer on top of another already existing transport layers, this is very easy.

0.2.21

3 years ago

0.2.20

3 years ago

0.2.19

3 years ago

0.2.18

3 years ago

0.2.17

3 years ago

0.2.16

3 years ago

0.2.15

3 years ago

0.2.14

3 years ago

0.2.13

3 years ago

0.2.12

3 years ago

0.2.11

3 years ago

0.2.10

3 years ago

0.2.9

3 years ago

0.2.8

3 years ago

0.2.7

3 years ago

0.2.6

3 years ago

0.2.5

3 years ago

0.2.4

4 years ago

0.2.3

4 years ago

0.2.2

4 years ago

0.2.1

4 years ago

0.2.0

4 years ago

0.1.11

4 years ago

0.1.10

4 years ago

0.1.8

4 years ago

0.1.9

4 years ago

0.1.7

4 years ago

0.1.6

4 years ago

0.1.5

4 years ago

0.1.4

4 years ago

0.1.3

4 years ago

0.1.2

4 years ago

0.1.1

4 years ago

0.1.0

4 years ago