Browse Source

add timeout parameter to client socket

master
nitowa 3 years ago
parent
commit
bf96f9007f
8 changed files with 54 additions and 22 deletions
  1. 4
    3
      src/Backend.ts
  2. 15
    6
      src/Frontend.ts
  3. 0
    1
      src/Interfaces.ts
  4. 1
    0
      src/PromiseIO/Client.ts
  5. 14
    0
      src/Strings.ts
  6. 2
    1
      src/Types.ts
  7. 4
    9
      src/Utils.ts
  8. 14
    2
      test/Test.ts

+ 4
- 3
src/Backend.ts View File

5
 import * as T from './Types';
5
 import * as T from './Types';
6
 import * as U from './Utils';
6
 import * as U from './Utils';
7
 import * as I from './Interfaces';
7
 import * as I from './Interfaces';
8
+import { BAD_CONFIG_PARAM, UNKNOWN_RPC_IDENTIFIER, UNKNOWN_RPC_SERVER } from './Strings';
8
 
9
 
9
 export class RPCServer<
10
 export class RPCServer<
10
     InterfaceT extends T.RPCInterface = T.RPCInterface,
11
     InterfaceT extends T.RPCInterface = T.RPCInterface,
45
             if (conf.errorHandler) conf.errorHandler(socket, error, rpcName, args)
46
             if (conf.errorHandler) conf.errorHandler(socket, error, rpcName, args)
46
             else {
47
             else {
47
                 if (forward) {
48
                 if (forward) {
48
-                    socket.call("$UNKNOWNRPC$", error)
49
+                    socket.call(UNKNOWN_RPC_IDENTIFIER, error)
49
                 } else {
50
                 } else {
50
                     throw error
51
                     throw error
51
                 }
52
                 }
96
             this.attach(undefined, options)
97
             this.attach(undefined, options)
97
         } else {
98
         } else {
98
             if (options)
99
             if (options)
99
-                console.warn("RPCServer options were passed to listen(..) after attach(..) was called. Please pass them to attach(..) instead. Ignoring.")
100
+                console.warn(BAD_CONFIG_PARAM)
100
         }
101
         }
101
         this.pio.listen(port)
102
         this.pio.listen(port)
102
         return this
103
         return this
114
                 clientSocket.on("*", (packet) => {
115
                 clientSocket.on("*", (packet) => {
115
                     if (!infos.some(i => i.uniqueName === packet.data[0])) {
116
                     if (!infos.some(i => i.uniqueName === packet.data[0])) {
116
                         if (packet.data[0].startsWith('destroy_')) return
117
                         if (packet.data[0].startsWith('destroy_')) return
117
-                        this.errorHandler(clientSocket, new Error(`Unknown RPC ${packet.data[0]}`), packet.data[0], [...packet.data].splice(1), true)
118
+                        this.errorHandler(clientSocket, new Error(UNKNOWN_RPC_SERVER(packet.data[0])), packet.data[0], [...packet.data].splice(1), true)
118
                     }
119
                     }
119
                 })
120
                 })
120
             }
121
             }

+ 15
- 6
src/Frontend.ts View File

4
 import * as T from './Types';
4
 import * as T from './Types';
5
 import * as I from './Interfaces';
5
 import * as I from './Interfaces';
6
 import { stripAfterEquals, appendComma } from './Utils';
6
 import { stripAfterEquals, appendComma } from './Utils';
7
+import { SOCKET_NOT_CONNECTED, UNKNOWN_RPC_IDENTIFIER, USER_DEFINED_TIMEOUT } from './Strings';
7
 
8
 
8
 
9
 
9
 /**
10
 /**
33
      */
34
      */
34
     constructor(public port: number, public address: string, private conf: T.ClientConfig = defaultClientConfig) {
35
     constructor(public port: number, public address: string, private conf: T.ClientConfig = defaultClientConfig) {
35
         Object.defineProperty(this, 'socket', { value: undefined, writable: true })
36
         Object.defineProperty(this, 'socket', { value: undefined, writable: true })
36
-        this.hook("$UNKNOWNRPC$", (err) => this.handlers['error'].forEach(handler => handler(err)))
37
+        this.hook(UNKNOWN_RPC_IDENTIFIER, (err) => this.handlers['error'].forEach(handler => handler(err)))
37
     }
38
     }
38
 
39
 
39
     /**
40
     /**
114
      * @param args other arguments
115
      * @param args other arguments
115
      */
116
      */
116
     public async call(rpcname: string, ...args: any[]): Promise<any> {
117
     public async call(rpcname: string, ...args: any[]): Promise<any> {
117
-        if (!this.socket) throw new Error("The socket is not connected! Use socket.connect() first")
118
+        if (!this.socket) throw new Error(SOCKET_NOT_CONNECTED)
119
+
118
 
120
 
119
         try {
121
         try {
120
-            const val = await this.socket.call.apply(this.socket, [rpcname, ...args])
121
-            return val
122
+            if(!this.conf.callTimeoutMs || this.conf.callTimeoutMs <= 0)
123
+                return await this.socket.call.apply(this.socket, [rpcname, ...args])
124
+            else 
125
+                return await Promise.race([
126
+                    this.socket.call.apply(this.socket, [rpcname, ...args]),
127
+                    new Promise((_, rej) => {
128
+                        setTimeout(_ => rej(USER_DEFINED_TIMEOUT(this.conf.callTimeoutMs)), this.conf.callTimeoutMs)
129
+                    }) 
130
+                ])
122
         } catch (e) {
131
         } catch (e) {
123
             this.emit('error', e)
132
             this.emit('error', e)
124
             throw e
133
             throw e
131
      * @param args other arguments
140
      * @param args other arguments
132
      */
141
      */
133
     public async fire(rpcname: string, ...args: any[]): Promise<void> {
142
     public async fire(rpcname: string, ...args: any[]): Promise<void> {
134
-        if (!this.socket) throw new Error("The socket is not connected! Use socket.connect() first")
143
+        if (!this.socket) throw new Error(SOCKET_NOT_CONNECTED)
135
         await this.socket.fire.apply(this.socket, [rpcname, ...args])
144
         await this.socket.fire.apply(this.socket, [rpcname, ...args])
136
     }
145
     }
137
 
146
 
181
      * Get a list of available RPCs from the server
190
      * Get a list of available RPCs from the server
182
      */
191
      */
183
     public async info(sesame?: string) {
192
     public async info(sesame?: string) {
184
-        if (!this.socket) throw new Error("The socket is not connected! Use socket.connect() first")
193
+        if (!this.socket) throw new Error(SOCKET_NOT_CONNECTED)
185
         return await this.socket.call('info', sesame)
194
         return await this.socket.call('info', sesame)
186
     }
195
     }
187
 
196
 

+ 0
- 1
src/Interfaces.ts View File

1
 import * as T from "./Types";
1
 import * as T from "./Types";
2
-import * as I from "./Interfaces"
3
 
2
 
4
 /**
3
 /**
5
  * Interface for all classes that export RPCs
4
  * Interface for all classes that export RPCs

+ 1
- 0
src/PromiseIO/Client.ts View File

10
     reconnectionDelay: 200,
10
     reconnectionDelay: 200,
11
     timeout: 450,
11
     timeout: 450,
12
     reconnection: false,
12
     reconnection: false,
13
+    callTimeoutMs: 0,
13
 } 
14
 } 
14
 
15
 
15
 export class PromiseIOClient {
16
 export class PromiseIOClient {

+ 14
- 0
src/Strings.ts View File

1
+export const USER_DEFINED_TIMEOUT = ms => `User defined socket timout after ${ms}ms`
2
+export const SOCKET_NOT_CONNECTED = "The socket is not connected! Use socket.connect() first"
3
+export const UNKNOWN_RPC_IDENTIFIER = "$UNKNOWNRPC$"
4
+export const UNKNOWN_RPC_SERVER = name => `Unknown RPC ${name}`
5
+export const RPC_BAD_TYPE = type => `Bad socketIORPC type ${type}`
6
+export const CALL_NOT_FOUND = callName => `Call not found: ${callName}. ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: ${callName}`
7
+export const BAD_CONFIG_PARAM = "RPCServer options were passed to listen(..) after attach(..) was called. Please pass them to attach(..) instead. Ignoring."
8
+export const RPC_NO_NAME = name => `
9
+RPC did not provide a name. 
10
+\nUse 'funtion name(..){ .. }' syntax instead.
11
+\n
12
+\n<------------OFFENDING RPC:
13
+\n${name}
14
+\n>------------OFFENDING RPC`

+ 2
- 1
src/Types.ts View File

21
     'close' : () => void
21
     'close' : () => void
22
 }
22
 }
23
 export type ClientConfig = SocketIOClient.ConnectOpts & {
23
 export type ClientConfig = SocketIOClient.ConnectOpts & {
24
-    protocol?: 'http' | 'https'
24
+    protocol?: 'http' | 'https',
25
+    callTimeoutMs?: number
25
 }
26
 }
26
 export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT>[]
27
 export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT>[]
27
 
28
 

+ 4
- 9
src/Utils.ts View File

3
 import * as T from "./Types";
3
 import * as T from "./Types";
4
 import * as I from "./Interfaces";
4
 import * as I from "./Interfaces";
5
 import { Socket } from "socket.io"
5
 import { Socket } from "socket.io"
6
+import { CALL_NOT_FOUND, RPC_BAD_TYPE, RPC_NO_NAME } from "./Strings";
6
 
7
 
7
 /**
8
 /**
8
  * Translate an RPC to RPCInfo for serialization.
9
  * Translate an RPC to RPCInfo for serialization.
36
                 }
37
                 }
37
             }
38
             }
38
         case "function":
39
         case "function":
39
-            if (!rpc.name) throw new Error(`
40
-RPC did not provide a name. 
41
-\nUse 'funtion name(..){ .. }' syntax instead.
42
-\n
43
-\n<------------OFFENDING RPC:
44
-\n${rpc.toString()}
45
-\n>------------OFFENDING RPC`)
40
+            if (!rpc.name) throw new Error(RPC_NO_NAME(rpc.toString()))
46
             return {
41
             return {
47
                 owner: owner,
42
                 owner: owner,
48
                 argNames: extractArgs(rpc),
43
                 argNames: extractArgs(rpc),
51
                 call: sesame ? async ($__sesame__$, ...args) => { if (sesame($__sesame__$)) return await rpc.apply({}, args); throw makeError(rpc.name) } : rpc, // check & remove sesame 
46
                 call: sesame ? async ($__sesame__$, ...args) => { if (sesame($__sesame__$)) return await rpc.apply({}, args); throw makeError(rpc.name) } : rpc, // check & remove sesame 
52
             }
47
             }
53
     }
48
     }
54
-    throw new Error("Bad socketIORPC type " + typeof rpc)
49
+    throw new Error(RPC_BAD_TYPE(typeof rpc))
55
 }
50
 }
56
 
51
 
57
 /**
52
 /**
141
     return eval(hookStr)
136
     return eval(hookStr)
142
 }
137
 }
143
 
138
 
144
-const makeError = (callName: string) => new Error(`Call not found: ${callName}. ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: ${callName}`)
139
+const makeError = (callName: string) => new Error(CALL_NOT_FOUND(callName))
145
 
140
 
146
 /**
141
 /**
147
  * Extract a string list of parameters from a function
142
  * Extract a string list of parameters from a function

+ 14
- 2
test/Test.ts View File

9
 import { PromiseIO } from "../src/PromiseIO/Server";
9
 import { PromiseIO } from "../src/PromiseIO/Server";
10
 import { PromiseIOClient } from "../src/PromiseIO/Client";
10
 import { PromiseIOClient } from "../src/PromiseIO/Client";
11
 import { assert, expect } from 'chai';
11
 import { assert, expect } from 'chai';
12
+import { USER_DEFINED_TIMEOUT } from "../src/Strings";
12
 var should = require('chai').should();
13
 var should = require('chai').should();
13
-
14
+var chai = require("chai");
15
+var chaiAsPromised = require("chai-as-promised");
16
+ 
17
+chai.use(chaiAsPromised);
14
 const noop = (...args) => { }
18
 const noop = (...args) => { }
15
 
19
 
16
 const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
20
 const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
311
             ],
315
             ],
312
         }
316
         }
313
     ]
317
     ]
318
+    const callTimeout = 100;
314
 
319
 
315
     let client: RPCSocket, client2: RPCSocket, server: RPCServer, server2: RPCServer
320
     let client: RPCSocket, client2: RPCSocket, server: RPCServer, server2: RPCServer
316
 
321
 
334
 
339
 
335
         new RPCSocket(8080, 'localhost').connect().then(sock => {
340
         new RPCSocket(8080, 'localhost').connect().then(sock => {
336
             client = sock
341
             client = sock
337
-            new RPCSocket(8080, 'localhost', { path: "test" }).connect().then(sock2 => {
342
+            new RPCSocket(8080, 'localhost', { path: "test", callTimeoutMs: callTimeout }).connect().then(sock2 => {
338
                 client2 = sock2
343
                 client2 = sock2
339
                 done()
344
                 done()
340
             })
345
             })
356
         const res2 = await client2['Grp2'].test()
361
         const res2 = await client2['Grp2'].test()
357
         expect(res2).to.equal('/test')
362
         expect(res2).to.equal('/test')
358
     })
363
     })
364
+
365
+    it('server1 should answer after server2 is closed', async () => {
366
+        server2.close()
367
+        const res = await client['HelloWorldRPCGroup'].echo("test")
368
+        expect(res).to.equal('test')
369
+        return client2['Grp2'].test().should.eventually.be.rejectedWith(USER_DEFINED_TIMEOUT(callTimeout))
370
+    })
359
 })
371
 })
360
 
372
 
361
 describe("can attach second RPCServer if first is already running", () => {
373
 describe("can attach second RPCServer if first is already running", () => {

Loading…
Cancel
Save