From f64a9b8b6c24a004f458ed352d946afa335c415e Mon Sep 17 00:00:00 2001 From: Angus Fenying Date: Wed, 15 Apr 2020 17:33:22 +0800 Subject: [PATCH] perf(encoder): fixed the debuff of encoder --- .gitignore | 1 + .vscode/launch.json | 14 +++++ CHANGES.md | 4 ++ package-lock.json | 2 +- package.json | 2 +- src/benchmarks/Decoder.ts | 16 ++++++ src/benchmarks/Encoder.ts | 22 +++++++- src/benchmarks/Http/API.ts | 34 ++++++++++++ src/benchmarks/Http/Client.ts | 50 +++++++++++++++++ src/benchmarks/Http/Server.ts | 48 ++++++++++++++++ src/benchmarks/TCP/API.ts | 34 ++++++++++++ src/benchmarks/TCP/Client.ts | 65 ++++++++++++++++++++++ src/benchmarks/TCP/Server.ts | 48 ++++++++++++++++ src/examples/Http.ts | 46 +++++++++++++--- src/examples/TCP.ts | 50 +++++++++++++---- src/lib/Client/Common.ts | 29 +++++++++- src/lib/Client/Errors.ts | 16 ++++++ src/lib/Client/HttpClient.ts | 63 +++++++++++++++++---- src/lib/Client/TCPClient.ts | 94 +++++++++++++++++++++++++------- src/lib/Client/index.ts | 16 ++++++ src/lib/Common/API.ts | 23 +++++--- src/lib/Common/Encoding.ts | 18 ++++++ src/lib/Common/Request.ts | 22 +++++++- src/lib/Common/Response.ts | 24 ++++++-- src/lib/Common/index.ts | 16 ++++++ src/lib/Encoder/Decoder.ts | 62 +++++++++++++++++---- src/lib/Encoder/Encoder.ts | 52 ++++++++---------- src/lib/Encoder/Errors.ts | 16 ++++++ src/lib/Errors.ts | 16 ++++++ src/lib/Server/Common/Gateway.ts | 16 ++++++ src/lib/Server/Common/Request.ts | 30 ++++++---- src/lib/Server/Common/Router.ts | 24 +++++++- src/lib/Server/Common/Server.ts | 16 ++++++ src/lib/Server/Common/index.ts | 16 ++++++ src/lib/Server/Errors.ts | 16 ++++++ src/lib/Server/Gateways/Http.ts | 20 ++++++- src/lib/Server/Gateways/TCP.ts | 19 +++++++ src/lib/Server/Request.ts | 52 ------------------ src/lib/Server/Server.ts | 61 ++++++++++++++------- src/lib/Server/SimpleRouter.ts | 34 ++++++++++-- src/lib/Server/index.ts | 16 ++++++ src/lib/index.ts | 16 ++++++ 42 files changed, 1034 insertions(+), 205 deletions(-) create mode 100644 src/benchmarks/Http/API.ts create mode 100644 src/benchmarks/Http/Client.ts create mode 100644 src/benchmarks/Http/Server.ts create mode 100644 src/benchmarks/TCP/API.ts create mode 100644 src/benchmarks/TCP/Client.ts create mode 100644 src/benchmarks/TCP/Server.ts delete mode 100644 src/lib/Server/Request.ts diff --git a/.gitignore b/.gitignore index 07584d0..547bb95 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /test/ /dist/ /benchmarks/ +/*.log diff --git a/.vscode/launch.json b/.vscode/launch.json index f61ab28..616fdea 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,6 +32,20 @@ "${workspaceFolder}/examples/**/*.js" ] }, + { + "type": "node", + "request": "launch", + "name": "[Televoke] Http", + "program": "${workspaceFolder}/src/examples/Http.ts", + "sourceMaps": true, + "cwd": "${workspaceFolder}", + "outFiles": [ + "${workspaceFolder}/lib/*.js", + "${workspaceFolder}/lib/**/*.js", + "${workspaceFolder}/examples/*.js", + "${workspaceFolder}/examples/**/*.js" + ] + }, { "type": "node", "request": "launch", diff --git a/CHANGES.md b/CHANGES.md index 73cc395..7d621e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1 +1,5 @@ # Changes Logs + +## v0.2.0 + +- refactor(global): Improved the experience. diff --git a/package-lock.json b/package-lock.json index af95a95..5e951fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@litert/televoke", - "version": "0.1.1", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a755a43..718213f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@litert/televoke", - "version": "0.1.1", + "version": "0.2.0", "description": "A simple RPC service framework.", "main": "lib/index.js", "scripts": { diff --git a/src/benchmarks/Decoder.ts b/src/benchmarks/Decoder.ts index 5e7dae1..01d5dea 100644 --- a/src/benchmarks/Decoder.ts +++ b/src/benchmarks/Decoder.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as $Televoke from '../lib'; const decoder = $Televoke.createDecoder(); diff --git a/src/benchmarks/Encoder.ts b/src/benchmarks/Encoder.ts index 31f1c9c..fc49607 100644 --- a/src/benchmarks/Encoder.ts +++ b/src/benchmarks/Encoder.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as $Televoke from '../lib'; const encoder = $Televoke.createEncoder(); @@ -6,7 +22,8 @@ const writer: $Televoke.IWritable = { write(b, c) { if (c) { c(); } - } + }, + writable: true }; const data = { @@ -56,7 +73,8 @@ function testAsync(times: number): void { write(b, c) { if (c) { setImmediate(c); } - } + }, + writable: true }, data); if (!(times-- && setImmediate(testAsync, times))) { diff --git a/src/benchmarks/Http/API.ts b/src/benchmarks/Http/API.ts new file mode 100644 index 0000000..a2eb51f --- /dev/null +++ b/src/benchmarks/Http/API.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as $Televoke from '../../lib'; + +export const BENCHMARK_SERVER_HOST = '127.0.0.1'; +export const BENCHMARK_SERVER_PORT = 9988; + +export interface IGreetArguments { + + name: string; +} + +export interface IGa extends $Televoke.IServiceAPIs { + + hi(data: IGreetArguments): string; + + Hello(data: IGreetArguments): string; + + TestError(data: IGreetArguments): string; +} diff --git a/src/benchmarks/Http/Client.ts b/src/benchmarks/Http/Client.ts new file mode 100644 index 0000000..2cbdc39 --- /dev/null +++ b/src/benchmarks/Http/Client.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as $Televoke from '../../lib'; +import { IGa, BENCHMARK_SERVER_PORT, BENCHMARK_SERVER_HOST } from './API'; + +(async () => { + + const client = $Televoke.createHttpClient(BENCHMARK_SERVER_HOST, BENCHMARK_SERVER_PORT, Math.random); + + await client.connect(); + + console.time('TCP Invoke Concurrent'); + await Promise.all(Array(10000).fill(0).map(() => client.invoke('hi', {name: 'Angus'}))); + console.timeEnd('TCP Invoke Concurrent'); + + console.time('TCP Invoke Sequence'); + for (let i = 0; i < 10000; i++) { + + await client.invoke('hi', {name: 'Angus'}); + } + console.timeEnd('TCP Invoke Sequence'); + + console.time('TCP Call Concurrent'); + await Promise.all(Array(10000).fill(0).map(() => client.call('hi', {name: 'Angus'}))); + console.timeEnd('TCP Call Concurrent'); + + console.time('TCP Call Sequence'); + for (let i = 0; i < 10000; i++) { + + await client.call('hi', {name: 'Angus'}); + } + console.timeEnd('TCP Call Sequence'); + + await client.close(); + +})().catch(console.error); diff --git a/src/benchmarks/Http/Server.ts b/src/benchmarks/Http/Server.ts new file mode 100644 index 0000000..a7062c5 --- /dev/null +++ b/src/benchmarks/Http/Server.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as $Televoke from '../../lib'; +import { IGa, BENCHMARK_SERVER_PORT, BENCHMARK_SERVER_HOST } from './API'; + +(async () => { + + const router = $Televoke.createSimpleRouter(); + + router.add('hi', async function(data) { + + return `Hi, ${data.name}`; + }); + + router.register('Hello', async function(_) { + + return `Hello, ${_.args[0].name}`; + }); + + router.add('TestError', async function(data) { + + throw `Hello, ${data.name}`; + }); + + const server = $Televoke.createServer(); + + server.setRouter(router); + server.on('error', console.error); + server.on('handler_error', console.error); + server.addGateway('tcp', $Televoke.createHttpGateway(BENCHMARK_SERVER_HOST, BENCHMARK_SERVER_PORT)); + + await server.start(); + +})().catch(console.error); diff --git a/src/benchmarks/TCP/API.ts b/src/benchmarks/TCP/API.ts new file mode 100644 index 0000000..a2eb51f --- /dev/null +++ b/src/benchmarks/TCP/API.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as $Televoke from '../../lib'; + +export const BENCHMARK_SERVER_HOST = '127.0.0.1'; +export const BENCHMARK_SERVER_PORT = 9988; + +export interface IGreetArguments { + + name: string; +} + +export interface IGa extends $Televoke.IServiceAPIs { + + hi(data: IGreetArguments): string; + + Hello(data: IGreetArguments): string; + + TestError(data: IGreetArguments): string; +} diff --git a/src/benchmarks/TCP/Client.ts b/src/benchmarks/TCP/Client.ts new file mode 100644 index 0000000..1ec0c7b --- /dev/null +++ b/src/benchmarks/TCP/Client.ts @@ -0,0 +1,65 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as $Televoke from '../../lib'; +import { IGa, BENCHMARK_SERVER_PORT, BENCHMARK_SERVER_HOST } from './API'; + +const ridGenerator = (function() { + + let i = 0; + return () => i++; +})(); + +const CONCURRENCY = 10000; + +(async () => { + + const client = $Televoke.createTCPClient(BENCHMARK_SERVER_HOST, BENCHMARK_SERVER_PORT, ridGenerator); + + await client.connect(); + + await Promise.all(Array(CONCURRENCY).fill(0).map(() => client.invoke('hi', {name: 'Angus'}))); + + for (let i = 0; i < CONCURRENCY; i++) { + + await client.invoke('hi', {name: 'Angus'}); + } + + console.time(`TCP ${CONCURRENCY} Invokes Concurrent`); + await Promise.all(Array(CONCURRENCY).fill(0).map(() => client.invoke('hi', {name: 'Angus'}))); + console.timeEnd(`TCP ${CONCURRENCY} Invokes Concurrent`); + + console.time(`TCP ${CONCURRENCY} Invokes Sequence`); + for (let i = 0; i < CONCURRENCY; i++) { + + await client.invoke('hi', {name: 'Angus'}); + } + console.timeEnd(`TCP ${CONCURRENCY} Invokes Sequence`); + + console.time(`TCP ${CONCURRENCY} Calls Concurrent`); + await Promise.all(Array(CONCURRENCY).fill(0).map(() => client.call('hi', {name: 'Angus'}))); + console.timeEnd(`TCP ${CONCURRENCY} Calls Concurrent`); + + console.time(`TCP ${CONCURRENCY} Calls Sequence`); + for (let i = 0; i < CONCURRENCY; i++) { + + await client.call('hi', {name: 'Angus'}); + } + console.timeEnd(`TCP ${CONCURRENCY} Calls Sequence`); + + await client.close(); + +})().catch(console.error); diff --git a/src/benchmarks/TCP/Server.ts b/src/benchmarks/TCP/Server.ts new file mode 100644 index 0000000..bce7508 --- /dev/null +++ b/src/benchmarks/TCP/Server.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as $Televoke from '../../lib'; +import { IGa, BENCHMARK_SERVER_PORT, BENCHMARK_SERVER_HOST } from './API'; + +(async () => { + + const router = $Televoke.createSimpleRouter(); + + router.add('hi', async function(data) { + + return `Hi, ${data.name}`; + }); + + router.register('Hello', async function(_) { + + return `Hello, ${_.args[0].name}`; + }); + + router.add('TestError', async function(data) { + + throw `Hello, ${data.name}`; + }); + + const server = $Televoke.createServer(); + + server.setRouter(router); + server.on('error', console.error); + server.on('handler_error', console.error); + server.addGateway('tcp', $Televoke.createTCPGateway(BENCHMARK_SERVER_HOST, BENCHMARK_SERVER_PORT)); + + await server.start(); + +})().catch(console.error); diff --git a/src/examples/Http.ts b/src/examples/Http.ts index f154949..7f5a824 100644 --- a/src/examples/Http.ts +++ b/src/examples/Http.ts @@ -1,27 +1,57 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as $Televoke from '../lib'; +interface IGreetArguments { + + name: string; +} + +interface IGa extends $Televoke.IServiceAPIs { + + hi(data: IGreetArguments): string; + + Hello(data: IGreetArguments): string; + + TestError(data: IGreetArguments): string; +} + (async () => { const router = $Televoke.createSimpleRouter(); - router.addHandler('hi', function(req) { + router.add('hi', async function(data) { - req.ok(`Hi, ${req.args.name}`); + return `Hi, ${data.name}`; }); - router.addHandler('Hello', function(req) { + router.register('Hello', async function(_) { - req.ok(`Hello, ${req.args.name}`); + return `Hello, ${_.args[0].name} (Request ID: ${_.rid})`; }); - router.addHandler('TestError', function(req) { + router.add('TestError', async function(data) { - req.fail(`Hello, ${req.args.name}`); + throw `Hello, ${data.name}`; }); const server = $Televoke.createServer(); - const client = $Televoke.createHttpClient('127.0.0.1', 8899, Math.random); + const client = $Televoke.createHttpClient('127.0.0.1', 8899, Math.random); server.setRouter(router); server.on('error', console.error); @@ -33,7 +63,7 @@ import * as $Televoke from '../lib'; await client.connect(); console.log(await client.invoke('hi', {'name': 'Mick'})); - console.log(await client.invoke('hi', {'name': 'Angus'})); + console.log(await client.call('Hello', {'name': 'Angus'})); await client.invoke('not-exists-api', {'name': 'V'}).catch((e) => console.error(e.toString())); await client.invoke('TestError', {'name': 'V'}).catch((e) => console.error(e.toString())); diff --git a/src/examples/TCP.ts b/src/examples/TCP.ts index 2e9d2dd..eb24ce6 100644 --- a/src/examples/TCP.ts +++ b/src/examples/TCP.ts @@ -1,27 +1,57 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as $Televoke from '../lib'; +interface IGreetArguments { + + name: string; +} + +interface IGa extends $Televoke.IServiceAPIs { + + hi(data: IGreetArguments): string; + + Hello(data: IGreetArguments): string; + + TestError(data: IGreetArguments): string; +} + (async () => { const router = $Televoke.createSimpleRouter(); - router.addHandler('hi', function(req) { + router.add('hi', async function(data) { - req.ok(`Hi, ${req.args.name}`); + return `Hi, ${data.name}`; }); - router.addHandler('Hello', function(req) { + router.register('Hello', async function(_) { - req.ok(`Hello, ${req.args.name}`); + return `Hello, ${_.args[0].name}`; }); - router.addHandler('TestError', function(req) { + router.add('TestError', async function(data) { - req.fail(`Hello, ${req.args.name}`); + throw `Hello, ${data.name}`; }); const server = $Televoke.createServer(); - const client = $Televoke.createTCPClient('127.0.0.1', 8899, Math.random); + const client = $Televoke.createTCPClient('127.0.0.1', 8899, Math.random); server.setRouter(router); server.on('error', console.error); @@ -33,10 +63,10 @@ import * as $Televoke from '../lib'; await client.connect(); console.log(await client.invoke('hi', {'name': 'Mick'})); - console.log(await client.invoke('hi', {'name': 'Angus'})); + console.log(await client.call('Hello', {'name': 'Angus'})); - await client.invoke('not-exists-api', {'name': 'V'}).catch((e) => console.error(e.toString())); - await client.invoke('TestError', {'name': 'V'}).catch((e) => console.error(e.toString())); + await client.invoke('not-exists-api', {'name': 'V'}).catch((e) => console.error(e.toJSON())); + await client.invoke('TestError', {'name': 'V'}).catch((e) => console.error(e.toJSON())); await client.close(); diff --git a/src/lib/Client/Common.ts b/src/lib/Client/Common.ts index 59cab5e..2d2ccc4 100644 --- a/src/lib/Client/Common.ts +++ b/src/lib/Client/Common.ts @@ -1,12 +1,39 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as G from '../Common'; export type IRIDGenerator = () => string | number; +export interface IResponse extends G.IRawResponse { + + cst: number; + + crt: number; +} + export interface IClient { + onError: (e: unknown) => void; + connect(): Promise; - invoke(api: K, args: S[K]['arguments'], timeout?: number): Promise; + invoke(api: K, ...args: Parameters): Promise>; + + call(api: K, ...args: Parameters): Promise>; close(): Promise; } diff --git a/src/lib/Client/Errors.ts b/src/lib/Client/Errors.ts index 4dfb51e..6704cfc 100644 --- a/src/lib/Client/Errors.ts +++ b/src/lib/Client/Errors.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { ErrorHub } from '../Errors'; interface IResponseError { diff --git a/src/lib/Client/HttpClient.ts b/src/lib/Client/HttpClient.ts index d95742d..c58cee5 100644 --- a/src/lib/Client/HttpClient.ts +++ b/src/lib/Client/HttpClient.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as $Http from 'http'; import * as C from './Common'; import * as GE from '../Errors'; @@ -8,6 +24,8 @@ class HttpClient implements C.IClient { private _agent: $Http.Agent; + public onError!: any; + public constructor( private _host: string, private _port: number, @@ -22,14 +40,26 @@ class HttpClient implements C.IClient { }); } - public invoke(api: any, args: any, timeout: number = this._timeout): Promise { + public invoke(api: any, ...args: any[]): Promise { + + return this._call(api, false, ...args); + } + + public call(api: any, ...args: any[]): Promise { + + return this._call(api, true, ...args); + } + + public _call(api: any, returnRaw: boolean, ...args: any[]): Promise { const rid = this._ridGenerator(); + const CST = Date.now(); + const content = JSON.stringify({ - ttl: timeout, + ttl: this._timeout, rid, - sat: Date.now(), + cst: CST, args, api }); @@ -52,7 +82,7 @@ class HttpClient implements C.IClient { headers: { 'content-length': Buffer.byteLength(content) }, - timeout + timeout: this._timeout }, (resp) => { if (!resp.headers['content-length']) { @@ -90,39 +120,43 @@ class HttpClient implements C.IClient { }).on('end', () => { - let data: any; + let rawData: any; try { - data = JSON.parse(buf as any); + rawData = JSON.parse(buf as any); } catch { return reject(new GE.E_INVALID_PACKET()); } + const data = rawData as C.IResponse; + data.cst = CST; + data.crt = Date.now(); + switch (data.code) { case G.EResponseCode.OK: - resolve(data.body); + resolve(returnRaw ? data : data.body); break; case G.EResponseCode.SYSTEM_ERROR: reject(new E.E_SERVER_INTERNAL_ERROR({ - metadata: { api, requestId: data.rid, time: data.rat, details: data.body } + metadata: { api, requestId: data.rid, time: data.srt, details: data } })); break; case G.EResponseCode.FAILURE: reject(new E.E_SERVER_LOGIC_FAILURE({ - metadata: { api, requestId: data.rid, time: data.rat, details: data.body } + metadata: { api, requestId: data.rid, time: data.srt, details: data } })); break; case G.EResponseCode.API_NOT_FOUND: reject(new E.E_API_NOT_FOUND({ - metadata: { api, requestId: data.rid, time: data.rat, details: data.body } + metadata: { api, requestId: data.rid, time: data.srt, details: data } })); break; default: reject(new E.E_SERVER_UNKNOWN_ERROR({ - metadata: { api, requestId: data.rid, time: data.rat, details: data.body } + metadata: { api, requestId: data.rid, time: data.srt, details: data } })); break; } @@ -152,7 +186,12 @@ class HttpClient implements C.IClient { } } -export function createHttpClient(host: string, port: number, ridGenerator: C.IRIDGenerator, timeout?: number): C.IClient { +export function createHttpClient( + host: string, + port: number, + ridGenerator: C.IRIDGenerator, + timeout?: number +): C.IClient { return new HttpClient(host, port, ridGenerator, timeout); } diff --git a/src/lib/Client/TCPClient.ts b/src/lib/Client/TCPClient.ts index 5705880..a4febd6 100644 --- a/src/lib/Client/TCPClient.ts +++ b/src/lib/Client/TCPClient.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as $Net from 'net'; import * as C from './Common'; import * as G from '../Common'; @@ -18,6 +34,10 @@ interface IRequest { api: string; + cst: number; + + returnRaw: boolean; + pr: Promises.IPromiseHandle; timer?: NodeJS.Timeout; @@ -51,6 +71,8 @@ class TCPClient implements C.IClient { private _encoder = createEncoder(); + public onError!: (e: unknown) => void; + public constructor( private _host: string, private _port: number, @@ -62,7 +84,17 @@ class TCPClient implements C.IClient { this._closePrId = `litert:televoke:tcp:client:${this._clientId}:close`; } - public invoke(api: any, args: any, timeout: number = this._timeout): Promise { + public invoke(api: any, ...args: any[]): Promise { + + return this._call(api, false, args); + } + + public call(api: any, ...args: any[]): Promise { + + return this._call(api, true, args); + } + + private _call(api: any, returnRaw: boolean, args: any[]): Promise { const rid = this._ridGenerator(); @@ -72,19 +104,22 @@ class TCPClient implements C.IClient { case EStatus.IDLE: return Promise.reject(new E.E_CONN_NOT_READY()); - case EStatus.WORKING: + case EStatus.WORKING: { + const NOW = Date.now(); this._encoder.encode(this._socket, { - ttl: timeout, + ttl: this._timeout, rid, - sat: Date.now(), + cst: NOW, args, api }); this._sentQty++; this._sent[rid] = { api, + cst: NOW, + returnRaw, pr: pr = TCPClient._promises.createPromise(), - timer: timeout > 0 ? setTimeout(() => { + timer: this._timeout > 0 ? setTimeout(() => { const req = this._sent[rid]; @@ -93,7 +128,7 @@ class TCPClient implements C.IClient { delete this._sent[rid]; this._sentQty--; req.pr.reject(new E.E_REQUEST_TIMEOUT({ - metadata: { api: req.api, requestId: rid, time: Date.now(), details: null } + metadata: { api: req.api, requestId: rid, time: req.cst, details: null } })); } @@ -103,22 +138,26 @@ class TCPClient implements C.IClient { this._status = EStatus.IDLE; } - }, timeout) : undefined + }, this._timeout) : undefined }; break; - case EStatus.CONNECTING: + } + case EStatus.CONNECTING: { + const NOW = Date.now(); this._sendingQty++; this._sending[rid] = { api, pr: pr = TCPClient._promises.createPromise(), + cst: NOW, + returnRaw, packet: { - ttl: timeout ?? this._timeout, + ttl: this._timeout, rid, - sat: Date.now(), + cst: NOW, args, api }, - timer: timeout > 0 ? setTimeout(() => { + timer: this._timeout > 0 ? setTimeout(() => { let req = this._sent[rid]; @@ -129,7 +168,7 @@ class TCPClient implements C.IClient { this._sentQty--; req.pr.reject(new E.E_REQUEST_TIMEOUT({ - metadata: { api: req.api, requestId: rid, time: Date.now(), details: null } + metadata: { api: req.api, requestId: rid, time: req.cst, details: null } })); if (this._status === EStatus.CLOSING && !this._sentQty) { @@ -150,7 +189,7 @@ class TCPClient implements C.IClient { this._sendingQty--; req.pr.reject(new E.E_REQUEST_TIMEOUT({ - metadata: { api: req.api, requestId: rid, time: Date.now(), details: null } + metadata: { api: req.api, requestId: rid, time: req.cst, details: null } })); if (this._status === EStatus.CLOSING && !this._sentQty) { @@ -160,10 +199,11 @@ class TCPClient implements C.IClient { } } - }, timeout) : undefined + }, this._timeout) : undefined }; break; + } case EStatus.CLOSING: throw new E.E_CONN_CLOSING(); } @@ -201,7 +241,12 @@ class TCPClient implements C.IClient { const decoder = createDecoder(); - decoder.onData = (data: G.IRawResponse): void => { + decoder.onLogicError = this.onError; + decoder.onProtocolError = this.onError; + + decoder.onData = (rawData: G.IRawResponse): void => { + + const data = rawData as C.IResponse; const req = this._sent[data.rid]; @@ -209,6 +254,8 @@ class TCPClient implements C.IClient { return; } + data.cst = req.cst; + data.crt = Date.now(); this._sentQty--; delete this._sent[data.rid]; @@ -226,26 +273,26 @@ class TCPClient implements C.IClient { switch (data.code) { case G.EResponseCode.OK: - req.pr.resolve(data.body); + req.pr.resolve(req.returnRaw ? data : data.body); break; case G.EResponseCode.SYSTEM_ERROR: req.pr.reject(new E.E_SERVER_INTERNAL_ERROR({ - metadata: { api: req.api, requestId: data.rid, time: data.rat, details: data.body } + metadata: { api: req.api, requestId: data.rid, time: data.srt, details: data } })); break; case G.EResponseCode.FAILURE: req.pr.reject(new E.E_SERVER_LOGIC_FAILURE({ - metadata: { api: req.api, requestId: data.rid, time: data.rat, details: data.body } + metadata: { api: req.api, requestId: data.rid, time: data.srt, details: data } })); break; case G.EResponseCode.API_NOT_FOUND: req.pr.reject(new E.E_API_NOT_FOUND({ - metadata: { api: req.api, requestId: data.rid, time: data.rat, details: data.body } + metadata: { api: req.api, requestId: data.rid, time: data.srt, details: data } })); break; default: req.pr.reject(new E.E_SERVER_UNKNOWN_ERROR({ - metadata: { api: req.api, requestId: data.rid, time: data.rat, details: data.body } + metadata: { api: req.api, requestId: data.rid, time: data.srt, details: data } })); break; } @@ -338,7 +385,12 @@ class TCPClient implements C.IClient { } } -export function createTCPClient(host: string, port: number, ridGenerator: C.IRIDGenerator, timeout?: number): C.IClient { +export function createTCPClient( + host: string, + port: number, + ridGenerator: C.IRIDGenerator, + timeout?: number +): C.IClient { return new TCPClient(host, port, ridGenerator, timeout); } diff --git a/src/lib/Client/index.ts b/src/lib/Client/index.ts index 89c21f0..9044026 100644 --- a/src/lib/Client/index.ts +++ b/src/lib/Client/index.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + export * from './Errors'; export * from './Common'; export * from './TCPClient'; diff --git a/src/lib/Common/API.ts b/src/lib/Common/API.ts index ec21fcf..97bbc5f 100644 --- a/src/lib/Common/API.ts +++ b/src/lib/Common/API.ts @@ -1,11 +1,20 @@ -export interface IAPISchema { - - arguments: any; - - response: any; -} +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ export interface IServiceAPIs { - [key: string]: IAPISchema; + [key: string]: (...args: any[]) => any; } diff --git a/src/lib/Common/Encoding.ts b/src/lib/Common/Encoding.ts index 2a13370..7101ebd 100644 --- a/src/lib/Common/Encoding.ts +++ b/src/lib/Common/Encoding.ts @@ -1,6 +1,24 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + export interface IWritable { write(buf: Buffer | string, cb?: () => void): void; + + writable: boolean; } export interface IEncoder { diff --git a/src/lib/Common/Request.ts b/src/lib/Common/Request.ts index cbc2b65..d9fcba3 100644 --- a/src/lib/Common/Request.ts +++ b/src/lib/Common/Request.ts @@ -1,12 +1,28 @@ -export interface IRawRequest { +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface IRawRequest { ttl: number; api: string; - args: any; + args: T; - sat: number; + cst: number; rid: string | number; } diff --git a/src/lib/Common/Response.ts b/src/lib/Common/Response.ts index d978f80..43d76d0 100644 --- a/src/lib/Common/Response.ts +++ b/src/lib/Common/Response.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + export enum EResponseCode { OK, @@ -6,15 +22,15 @@ export enum EResponseCode { FAILURE } -export interface IRawResponse { +export interface IRawResponse { - sat: number; + sst: number; - rat: number; + srt: number; rid: string | number; code: number; - body: any; + body: T; } diff --git a/src/lib/Common/index.ts b/src/lib/Common/index.ts index df5f9ed..327aa44 100644 --- a/src/lib/Common/index.ts +++ b/src/lib/Common/index.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + export * from './Encoding'; export * from './Request'; export * from './Response'; diff --git a/src/lib/Encoder/Decoder.ts b/src/lib/Encoder/Decoder.ts index c747569..d22eb69 100644 --- a/src/lib/Encoder/Decoder.ts +++ b/src/lib/Encoder/Decoder.ts @@ -1,4 +1,19 @@ -import * as E from './Errors'; +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as GE from '../Errors'; import * as C from '../Common'; @@ -22,6 +37,10 @@ class Decoder implements C.IDecoder { private _packetLength!: number; + private _plBuf: Buffer = Buffer.allocUnsafe(4); + + private _plBufLength: number = 0; + public constructor() { this._buf = Buffer.allocUnsafe(65536); @@ -116,17 +135,40 @@ class Decoder implements C.IDecoder { } else { - /** - * Accept at least 4 bytes as the leading packet. - */ - if (chunk.length < 4) { + if (this._plBufLength) { - this.onProtocolError(new E.E_INVALID_LEADING_PACKET()); - this.reset(); - return; + if (this._plBufLength + chunk.byteLength >= 4) { + + chunk.copy(this._plBuf, this._plBufLength, 0, 4 - this._plBufLength); + + chunk = chunk.slice(4 - this._plBufLength); + this._plBufLength = 0; + this._packetLength = this._plBuf.readUInt32LE(0); + } + else { + + chunk.copy(this._plBuf, this._plBufLength); + + this._plBufLength += chunk.byteLength; + return; + } } + else { + + if (chunk.length >= 4) { + + this._packetLength = chunk.readUInt32LE(0); + chunk = chunk.slice(4); + this._plBufLength = 0; + } + else { - this._packetLength = chunk.readUInt32LE(0); + chunk.copy(this._plBuf, this._plBufLength); + + this._plBufLength = chunk.byteLength; + return; + } + } if (this._packetLength > 67108864) { // Maximum packet size is 64M @@ -134,8 +176,6 @@ class Decoder implements C.IDecoder { this.reset(); } - chunk = chunk.slice(4); - if (this._buf.byteLength < this._packetLength) { this._buf = Buffer.allocUnsafe(this._packetLength); diff --git a/src/lib/Encoder/Encoder.ts b/src/lib/Encoder/Encoder.ts index 07d63bb..4f90b2c 100644 --- a/src/lib/Encoder/Encoder.ts +++ b/src/lib/Encoder/Encoder.ts @@ -1,46 +1,38 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as C from '../Common'; class Encoder implements C.IEncoder { - private _buf: Buffer; - - private _bufOffset: number[]; - - private _cur: number = 0; - - public constructor(bufSize: number = 1024 * 8) { - - this._bufOffset = Array(bufSize / 4).fill(0).map((v, i) => i * 4); - this._buf = Buffer.allocUnsafe(bufSize); - } - public encode(socket: C.IWritable, content: any): void { content = JSON.stringify(content); - let offset = this._bufOffset[this._cur++]; + const contentLength = Buffer.byteLength(content); - if (undefined === offset) { + const ret = Buffer.allocUnsafe(contentLength + 4); - this._buf = Buffer.allocUnsafe(this._buf.byteLength * 2); + ret.writeUInt32LE(contentLength, 0); + ret.write(content, 4); - offset = this._bufOffset[this._cur - 2] + 4; + if (socket.writable) { - this._bufOffset = [ - ...this._bufOffset, - ...Array(this._bufOffset.length).fill(0).map((v, i) => offset + i * 4) - ]; + socket.write(ret); } - - let buf: Buffer = this._buf.slice(offset, offset + 4); - - buf.writeUInt32LE(Buffer.byteLength(content), 0); - socket.write(buf, () => { - - this._bufOffset[--this._cur] = offset; - }); - - socket.write(content); } } diff --git a/src/lib/Encoder/Errors.ts b/src/lib/Encoder/Errors.ts index b014a9a..16e0da5 100644 --- a/src/lib/Encoder/Errors.ts +++ b/src/lib/Encoder/Errors.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as E from '../Errors'; export const E_INVALID_LEADING_PACKET = E.ErrorHub.define( diff --git a/src/lib/Errors.ts b/src/lib/Errors.ts index eb34b49..cbf2efc 100644 --- a/src/lib/Errors.ts +++ b/src/lib/Errors.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as L from '@litert/core'; export const ErrorHub = L.createErrorHub('@litert/televoke'); diff --git a/src/lib/Server/Common/Gateway.ts b/src/lib/Server/Common/Gateway.ts index 66ed838..41f9197 100644 --- a/src/lib/Server/Common/Gateway.ts +++ b/src/lib/Server/Common/Gateway.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { IRawRequest, IRawResponse } from '../../Common'; export type IReplyCallbak = (data: IRawResponse) => void; diff --git a/src/lib/Server/Common/Request.ts b/src/lib/Server/Common/Request.ts index eb6b213..3d28bbb 100644 --- a/src/lib/Server/Common/Request.ts +++ b/src/lib/Server/Common/Request.ts @@ -1,16 +1,22 @@ -export interface IRequest { +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ - readonly timeoutAt: number; +import { IRawRequest } from '../../Common'; - readonly requestId: string | number; +export interface IRequest extends IRawRequest { - readonly args: any; - - readonly sentAt: number; - - readonly receivedAt: number; - - ok(returnValue: any): void; - - fail(error: any): void; + srt: number; } diff --git a/src/lib/Server/Common/Router.ts b/src/lib/Server/Common/Router.ts index ef2892f..06b4fc8 100644 --- a/src/lib/Server/Common/Router.ts +++ b/src/lib/Server/Common/Router.ts @@ -1,8 +1,26 @@ -import { IRequest } from '../Common'; +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -export type IHandler = (req: IRequest) => void | Promise; +import { IRequest } from './Request'; + +export type IHandler any> = (...args: Parameters) => Promise>; + +export type IHandlerEx any> = (req: IRequest>) => Promise>; export interface IRouter { - route(name: string): IHandler | void; + route(name: string): [IHandler, true] | [IHandlerEx, false] | void; } diff --git a/src/lib/Server/Common/Server.ts b/src/lib/Server/Common/Server.ts index f2bb2b5..182d8fc 100644 --- a/src/lib/Server/Common/Server.ts +++ b/src/lib/Server/Common/Server.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { IGateway } from './Gateway'; import { IRouter } from './Router'; import { Events } from '@litert/observable'; diff --git a/src/lib/Server/Common/index.ts b/src/lib/Server/Common/index.ts index 75d854a..bd83952 100644 --- a/src/lib/Server/Common/index.ts +++ b/src/lib/Server/Common/index.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + export * from './Gateway'; export * from './Request'; export * from './Router'; diff --git a/src/lib/Server/Errors.ts b/src/lib/Server/Errors.ts index fa1938b..04ecce0 100644 --- a/src/lib/Server/Errors.ts +++ b/src/lib/Server/Errors.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { ErrorHub } from '../Errors'; export const E_GATEWAY_STARTING = ErrorHub.define( diff --git a/src/lib/Server/Gateways/Http.ts b/src/lib/Server/Gateways/Http.ts index 51a06c4..40fa718 100644 --- a/src/lib/Server/Gateways/Http.ts +++ b/src/lib/Server/Gateways/Http.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as C from '../Common'; import * as G from '../../Common'; import * as E from '../Errors'; @@ -49,7 +65,9 @@ class HttpGateway implements C.IGateway { let offset: number = 0; - req.on('data', (chunk: Buffer) => { + resp.on('error', this.onError); + + req.on('error', this.onError).on('data', (chunk: Buffer) => { const index = offset; diff --git a/src/lib/Server/Gateways/TCP.ts b/src/lib/Server/Gateways/TCP.ts index 7cdaed8..52e050c 100644 --- a/src/lib/Server/Gateways/TCP.ts +++ b/src/lib/Server/Gateways/TCP.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as C from '../Common'; import * as G from '../../Common'; import * as E from '../Errors'; @@ -102,6 +118,9 @@ class TCPGateway implements C.IGateway { client.status = EClientStatus.CLOSING; delete this._clients[clientId]; }).on( + 'error', + this.onError + ).on( 'close', () => delete this._clients[clientId] ); diff --git a/src/lib/Server/Request.ts b/src/lib/Server/Request.ts deleted file mode 100644 index ae4fb1e..0000000 --- a/src/lib/Server/Request.ts +++ /dev/null @@ -1,52 +0,0 @@ -import * as C from './Common'; -import * as G from '../Common'; - -export class Request implements C.IRequest { - - private _sent!: boolean; - - public constructor( - private _raw: G.IRawRequest, - private _reply: C.IReplyCallbak - ) {} - - public readonly receivedAt = Date.now(); - - public get timeoutAt(): number { return this._raw.ttl + this.receivedAt; } - - public get requestId(): string | number { return this._raw.rid; } - - public get args(): any { return this._raw.args; } - - public get sentAt(): number { return this._raw.sat; } - - public ok(data: {}): void { - - if (this._sent) { return; } - - this._sent = true; - - this._reply({ - rid: this._raw.rid, - rat: this.receivedAt, - sat: this.receivedAt, - code: G.EResponseCode.OK, - body: data, - }); - } - - public fail(data: {}): void { - - if (this._sent) { return; } - - this._sent = true; - - this._reply({ - rid: this._raw.rid, - rat: this.receivedAt, - sat: this.receivedAt, - code: G.EResponseCode.FAILURE, - body: data, - }); - } -} diff --git a/src/lib/Server/Server.ts b/src/lib/Server/Server.ts index 3b23038..2b5b6f5 100644 --- a/src/lib/Server/Server.ts +++ b/src/lib/Server/Server.ts @@ -1,8 +1,23 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as C from './Common'; import * as G from '../Common'; import * as E from './Errors'; import * as L from '@litert/core'; -import { Request } from './Request'; import { Events } from '@litert/observable'; enum EStatus { @@ -40,41 +55,45 @@ class Server extends Events.EventEmitter implements C.IServer { const handler = this._router.route(rawReq.api); - const req = new Request(rawReq, reply); + const req = rawReq as C.IRequest; + + req.srt = Date.now(); if (!handler) { return reply({ - rid: rawReq.rid, - rat: req.receivedAt, - sat: req.receivedAt, + rid: req.rid, + srt: req.srt, + sst: Date.now(), code: G.EResponseCode.API_NOT_FOUND, body: null }); } - try { + let pr: Promise; - const result = handler(req); + if (handler[1]) { - if (result instanceof Promise) { - - result.catch((e) => this.emit('handler_error', e)); - } + pr = handler[0](...rawReq.args); } - catch (e) { - - reply({ - rid: rawReq.rid, - rat: req.receivedAt, - sat: req.receivedAt, - code: G.EResponseCode.API_NOT_FOUND, - body: null - }); + else { - this.emit('handler_error', e); + pr = handler[0](req); } + pr.then((body) => reply({ + rid: req.rid, + srt: req.srt, + sst: Date.now(), + code: G.EResponseCode.OK, + body + }), (body) => reply({ + rid: req.rid, + srt: req.srt, + sst: Date.now(), + code: G.EResponseCode.FAILURE, + body + })); }; return this; diff --git a/src/lib/Server/SimpleRouter.ts b/src/lib/Server/SimpleRouter.ts index ba81f3b..615714d 100644 --- a/src/lib/Server/SimpleRouter.ts +++ b/src/lib/Server/SimpleRouter.ts @@ -1,19 +1,43 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as C from './Common'; export interface ISimpleRouter extends C.IRouter { - addHandler(apiName: string, handler: C.IHandler): this; + add any>(apiName: string, handler: C.IHandler): this; + + register any>(apiName: string, handler: C.IHandlerEx): this; removeHandler(apiName: string): this; } class SimpleRouter implements ISimpleRouter { - private _handlers: Record = {}; + private _handlers: Record = {}; + + public add any>(apiName: string, handler: C.IHandler): this { + + this._handlers[apiName] = [handler, true]; + return this; + } - public addHandler(apiName: string, handler: C.IHandler): this { + public register any>(apiName: string, handler: C.IHandlerEx): this { - this._handlers[apiName] = handler; + this._handlers[apiName] = [handler, false]; return this; } @@ -23,7 +47,7 @@ class SimpleRouter implements ISimpleRouter { return this; } - public route(name: string): C.IHandler | void { + public route(name: string): [any, boolean] | void { return this._handlers[name]; } diff --git a/src/lib/Server/index.ts b/src/lib/Server/index.ts index 6b461c5..91edee2 100644 --- a/src/lib/Server/index.ts +++ b/src/lib/Server/index.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + export * from './Common'; export * from './Server'; export * from './Errors'; diff --git a/src/lib/index.ts b/src/lib/index.ts index fabe961..75734b3 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2020 Angus.Fenying + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + export * from './Common'; export * from './Encoder/Encoder'; export * from './Encoder/Decoder';