From 219d4044243f9faf9bdf2474c3484012dfa0fb89 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sat, 12 Aug 2023 13:23:27 +0200 Subject: [PATCH] feat: add ws server request pool handlers example MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit reference: #790 Signed-off-by: Jérôme Benoit --- .eslintrc.js | 16 ++-- .github/dependabot.yml | 12 +++ CHANGELOG.md | 1 + README.md | 2 + .../typescript/http-client-pool/tsconfig.json | 1 + .../http-server-pool/express/src/main.ts | 2 +- .../http-server-pool/express/src/pool.ts | 10 +- .../http-server-pool/express/tsconfig.json | 1 + .../http-server-pool/fastify/package.json | 4 +- .../http-server-pool/fastify/tsconfig.json | 1 + .../websocket-server-pool/ws/package.json | 34 +++++++ .../websocket-server-pool/ws/pnpm-lock.yaml | 94 +++++++++++++++++++ .../websocket-server-pool/ws/requests.js | 20 ++++ .../websocket-server-pool/ws/src/main.ts | 55 +++++++++++ .../websocket-server-pool/ws/src/pool.ts | 26 +++++ .../websocket-server-pool/ws/src/types.ts | 21 +++++ .../websocket-server-pool/ws/src/worker.ts | 36 +++++++ .../websocket-server-pool/ws/tsconfig.json | 15 +++ 18 files changed, 337 insertions(+), 14 deletions(-) create mode 100644 examples/typescript/websocket-server-pool/ws/package.json create mode 100644 examples/typescript/websocket-server-pool/ws/pnpm-lock.yaml create mode 100644 examples/typescript/websocket-server-pool/ws/requests.js create mode 100644 examples/typescript/websocket-server-pool/ws/src/main.ts create mode 100644 examples/typescript/websocket-server-pool/ws/src/pool.ts create mode 100644 examples/typescript/websocket-server-pool/ws/src/types.ts create mode 100644 examples/typescript/websocket-server-pool/ws/src/worker.ts create mode 100644 examples/typescript/websocket-server-pool/ws/tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index 0188819b..d640b896 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -120,16 +120,16 @@ module.exports = defineConfig({ { files: ['examples/typescript/**/*.ts'], rules: { - 'import/no-unresolved': 'off', - '@typescript-eslint/no-unsafe-argument': 'off', + // 'import/no-unresolved': 'off', + // '@typescript-eslint/no-unsafe-argument': 'off', '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-return': 'off', + // '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unnecessary-type-assertion': 'off', - '@typescript-eslint/strict-boolean-expressions': 'off', - '@typescript-eslint/restrict-template-expressions': 'off', - '@typescript-eslint/return-await': 'off' + '@typescript-eslint/no-unsafe-member-access': 'off' + // '@typescript-eslint/no-unnecessary-type-assertion': 'off', + // '@typescript-eslint/strict-boolean-expressions': 'off', + // '@typescript-eslint/restrict-template-expressions': 'off', + // '@typescript-eslint/return-await': 'off' } }, { diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 34d0ba13..bc1d7c63 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -68,3 +68,15 @@ updates: - 'pioardi' - 'jerome-benoit' versioning-strategy: increase + - package-ecosystem: 'npm' + directory: '/examples/typescript/websocket-server-pool/ws' + schedule: + interval: 'daily' + labels: + - 'dependencies' + - 'examples' + - 'nocombine' + reviewers: + - 'pioardi' + - 'jerome-benoit' + versioning-strategy: increase diff --git a/CHANGELOG.md b/CHANGELOG.md index 95fbef8b..80df7b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add array of transferable objects to the `execute()` method arguments. +- WebSocket server pool examples: ws ## [2.6.23] - 2023-08-11 diff --git a/README.md b/README.md index d56bb187..570f61b3 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,8 @@ You can do the same with the classes _ClusterWorker_, _FixedClusterPool_ and _Dy - [HTTP server pool](./examples/typescript/http-server-pool/) - [Express](./examples/typescript/http-server-pool/express/) - [Fastify](./examples/typescript/http-server-pool/fastify/) + - [Websocket server pool](./examples/typescript/websocket-server-pool/) + - [ws](./examples/typescript/websocket-server-pool/ws/) Remember that workers can only send and receive structured-cloneable data. diff --git a/examples/typescript/http-client-pool/tsconfig.json b/examples/typescript/http-client-pool/tsconfig.json index 079bc9e1..c070ed24 100644 --- a/examples/typescript/http-client-pool/tsconfig.json +++ b/examples/typescript/http-client-pool/tsconfig.json @@ -4,6 +4,7 @@ "target": "es2022", "module": "es2022", "moduleResolution": "Node16", + "verbatimModuleSyntax": true, "rootDir": "./src", "outDir": "./dist", "esModuleInterop": true, diff --git a/examples/typescript/http-server-pool/express/src/main.ts b/examples/typescript/http-server-pool/express/src/main.ts index b7a713e0..4f1a4d95 100644 --- a/examples/typescript/http-server-pool/express/src/main.ts +++ b/examples/typescript/http-server-pool/express/src/main.ts @@ -30,7 +30,7 @@ expressApp.all('/api/echo', (req: Request, res: Response) => { expressApp.get('/api/factorial/:number', (req: Request, res: Response) => { const { number } = req.params requestHandlerPool - .execute({ body: { number } }, 'factorial') + .execute({ body: { number: parseInt(number) } }, 'factorial') .then(response => { return res.send(response.body).end() }) diff --git a/examples/typescript/http-server-pool/express/src/pool.ts b/examples/typescript/http-server-pool/express/src/pool.ts index 46b673a5..af2a216d 100644 --- a/examples/typescript/http-server-pool/express/src/pool.ts +++ b/examples/typescript/http-server-pool/express/src/pool.ts @@ -1,7 +1,11 @@ import { dirname, extname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { DynamicThreadPool, availableParallelism } from 'poolifier' -import { type WorkerData, type WorkerResponse } from './types.js' +import { + type BodyPayload, + type WorkerData, + type WorkerResponse +} from './types.js' const workerFile = join( dirname(fileURLToPath(import.meta.url)), @@ -9,8 +13,8 @@ const workerFile = join( ) export const requestHandlerPool = new DynamicThreadPool< -WorkerData, -WorkerResponse +WorkerData, +WorkerResponse >(1, availableParallelism(), workerFile, { enableTasksQueue: true, tasksQueueOptions: { diff --git a/examples/typescript/http-server-pool/express/tsconfig.json b/examples/typescript/http-server-pool/express/tsconfig.json index 82728d1c..57e49a1c 100644 --- a/examples/typescript/http-server-pool/express/tsconfig.json +++ b/examples/typescript/http-server-pool/express/tsconfig.json @@ -4,6 +4,7 @@ "target": "ES2022", "module": "ES2022", "moduleResolution": "Node16", + "verbatimModuleSyntax": true, "rootDir": "./src", "outDir": "./dist", "esModuleInterop": true, diff --git a/examples/typescript/http-server-pool/fastify/package.json b/examples/typescript/http-server-pool/fastify/package.json index 1f4f1172..ffc88cd7 100644 --- a/examples/typescript/http-server-pool/fastify/package.json +++ b/examples/typescript/http-server-pool/fastify/package.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/package", - "name": "express-request-pool", + "name": "fastify-plugin-request-pool", "version": "1.0.0", - "description": "Express request pool", + "description": "fastify plugin request pool", "main": "dist/main.js", "type": "module", "volta": { diff --git a/examples/typescript/http-server-pool/fastify/tsconfig.json b/examples/typescript/http-server-pool/fastify/tsconfig.json index 82728d1c..57e49a1c 100644 --- a/examples/typescript/http-server-pool/fastify/tsconfig.json +++ b/examples/typescript/http-server-pool/fastify/tsconfig.json @@ -4,6 +4,7 @@ "target": "ES2022", "module": "ES2022", "moduleResolution": "Node16", + "verbatimModuleSyntax": true, "rootDir": "./src", "outDir": "./dist", "esModuleInterop": true, diff --git a/examples/typescript/websocket-server-pool/ws/package.json b/examples/typescript/websocket-server-pool/ws/package.json new file mode 100644 index 00000000..7ef6bd68 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws/package.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json.schemastore.org/package", + "name": "ws-request-pool", + "version": "1.0.0", + "description": "ws request pool", + "main": "dist/main.js", + "type": "module", + "volta": { + "node": "20.5.1", + "pnpm": "8.6.12" + }, + "scripts": { + "build": "pnpm build:clean && tsc", + "build:clean": "tsc --build --clean", + "start": "node dist/main.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "poolifier": "^2.6.23", + "ws": "^8.13.0" + }, + "devDependencies": { + "@types/node": "^20.4.9", + "@types/ws": "^8.5.5", + "typescript": "^5.1.6" + }, + "optionalDependencies": { + "bufferutil": "^4.0.7", + "utf-8-validate": "^6.0.3" + } +} diff --git a/examples/typescript/websocket-server-pool/ws/pnpm-lock.yaml b/examples/typescript/websocket-server-pool/ws/pnpm-lock.yaml new file mode 100644 index 00000000..78402dc9 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws/pnpm-lock.yaml @@ -0,0 +1,94 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + poolifier: + specifier: ^2.6.23 + version: 2.6.23 + ws: + specifier: ^8.13.0 + version: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) + +optionalDependencies: + bufferutil: + specifier: ^4.0.7 + version: 4.0.7 + utf-8-validate: + specifier: ^6.0.3 + version: 6.0.3 + +devDependencies: + '@types/node': + specifier: ^20.4.9 + version: 20.4.10 + '@types/ws': + specifier: ^8.5.5 + version: 8.5.5 + typescript: + specifier: ^5.1.6 + version: 5.1.6 + +packages: + + /@types/node@20.4.10: + resolution: {integrity: sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==} + dev: true + + /@types/ws@8.5.5: + resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==} + dependencies: + '@types/node': 20.4.10 + dev: true + + /bufferutil@4.0.7: + resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.0 + dev: false + + /node-gyp-build@4.6.0: + resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} + hasBin: true + requiresBuild: true + dev: false + + /poolifier@2.6.23: + resolution: {integrity: sha512-QQagtUD4DCzeEEq2SjoxDRsPLqr5qzJm3Wur29rWDepG935BXlGHVZLFlqUoaHXkcILIBKvLxEFs+SB2Op4TIw==} + engines: {node: '>=16.14.0', pnpm: '>=8.6.0'} + requiresBuild: true + dev: false + + /typescript@5.1.6: + resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /utf-8-validate@6.0.3: + resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.0 + dev: false + + /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + bufferutil: 4.0.7 + utf-8-validate: 6.0.3 + dev: false diff --git a/examples/typescript/websocket-server-pool/ws/requests.js b/examples/typescript/websocket-server-pool/ws/requests.js new file mode 100644 index 00000000..e00bcc72 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws/requests.js @@ -0,0 +1,20 @@ +import { WebSocket } from 'ws' + +const ws = new WebSocket('ws://localhost:8080') + +ws.on('error', console.error) + +ws.on('open', () => { + for (let i = 0; i < 60; i++) { + ws.send( + JSON.stringify({ type: 'echo', data: { key1: 'value1', key2: 'value2' } }) + ) + } + for (let i = 0; i < 60; i++) { + ws.send(JSON.stringify({ type: 'factorial', data: { number: 30 } })) + } +}) + +ws.on('message', message => { + console.info('message received: %s', message) +}) diff --git a/examples/typescript/websocket-server-pool/ws/src/main.ts b/examples/typescript/websocket-server-pool/ws/src/main.ts new file mode 100644 index 00000000..37f31f3c --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws/src/main.ts @@ -0,0 +1,55 @@ +import { type RawData, WebSocketServer } from 'ws' +import { type DataPayload, type MessagePayload, MessageType } from './types.js' +import { requestHandlerPool } from './pool.js' + +const port = 8080 +const wss = new WebSocketServer({ port }, () => { + console.info( + `⚡️[ws server]: WebSocket server is started at http://localhost:${port}/` + ) +}) + +const emptyFunction = (): void => { + /** Intentional */ +} + +wss.on('connection', ws => { + ws.on('error', console.error) + + ws.on('message', (message: RawData) => { + const { type, data } = JSON.parse( + // eslint-disable-next-line @typescript-eslint/no-base-to-string + message.toString() + ) as MessagePayload + switch (type) { + case MessageType.echo: + requestHandlerPool + .execute({ data }, 'echo') + .then(response => { + ws.send( + JSON.stringify({ + type: MessageType.echo, + data: response.data + }) + ) + return null + }) + .catch(emptyFunction) + break + case MessageType.factorial: + requestHandlerPool + .execute({ data }, 'factorial') + .then(response => { + ws.send( + JSON.stringify({ + type: MessageType.factorial, + data: response.data + }) + ) + return null + }) + .catch(emptyFunction) + break + } + }) +}) diff --git a/examples/typescript/websocket-server-pool/ws/src/pool.ts b/examples/typescript/websocket-server-pool/ws/src/pool.ts new file mode 100644 index 00000000..b29c6d18 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws/src/pool.ts @@ -0,0 +1,26 @@ +import { dirname, extname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { DynamicThreadPool, availableParallelism } from 'poolifier' +import { + type DataPayload, + type WorkerData, + type WorkerResponse +} from './types.js' + +const workerFile = join( + dirname(fileURLToPath(import.meta.url)), + `worker${extname(fileURLToPath(import.meta.url))}` +) + +export const requestHandlerPool = new DynamicThreadPool< +WorkerData, +WorkerResponse +>(1, availableParallelism(), workerFile, { + enableTasksQueue: true, + tasksQueueOptions: { + concurrency: 8 + }, + errorHandler: (e: Error) => { + console.error(e) + } +}) diff --git a/examples/typescript/websocket-server-pool/ws/src/types.ts b/examples/typescript/websocket-server-pool/ws/src/types.ts new file mode 100644 index 00000000..4f821e67 --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws/src/types.ts @@ -0,0 +1,21 @@ +export enum MessageType { + echo = 'echo', + factorial = 'factorial' +} + +export interface MessagePayload { + type: MessageType + data: T +} + +export interface DataPayload { + number?: number +} + +export interface WorkerData { + data: T +} + +export interface WorkerResponse { + data: T +} diff --git a/examples/typescript/websocket-server-pool/ws/src/worker.ts b/examples/typescript/websocket-server-pool/ws/src/worker.ts new file mode 100644 index 00000000..92b9d64a --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws/src/worker.ts @@ -0,0 +1,36 @@ +import { ThreadWorker } from 'poolifier' +import { + type DataPayload, + type WorkerData, + type WorkerResponse +} from './types.js' + +const factorial: (n: number) => number = n => { + if (n === 0) { + return 1 + } + return factorial(n - 1) * n +} + +class RequestHandlerWorker< + Data extends WorkerData, + Response extends WorkerResponse +> extends ThreadWorker { + public constructor () { + super({ + echo: (workerData?: Data) => { + return workerData as unknown as Response + }, + factorial: (workerData?: Data) => { + return { + data: { number: factorial(workerData?.data?.number as number) } + } as unknown as Response + } + }) + } +} + +export const requestHandlerWorker = new RequestHandlerWorker< +WorkerData, +WorkerResponse +>() diff --git a/examples/typescript/websocket-server-pool/ws/tsconfig.json b/examples/typescript/websocket-server-pool/ws/tsconfig.json new file mode 100644 index 00000000..57e49a1c --- /dev/null +++ b/examples/typescript/websocket-server-pool/ws/tsconfig.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "Node16", + "verbatimModuleSyntax": true, + "rootDir": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} -- 2.34.1