Skip to content

Commit 70fa9f0

Browse files
Merge branch 'ivibe-my-fix-branch'
2 parents 48be3ef + 802be5c commit 70fa9f0

15 files changed

Lines changed: 935 additions & 26 deletions

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
"grpc": "1.19.0",
6060
"http2": "3.3.7",
6161
"iterare": "1.1.2",
62-
"json-socket": "0.3.0",
6362
"merge-graphql-schemas": "1.5.8",
6463
"mqtt": "2.18.8",
6564
"multer": "1.4.1",

packages/microservices/client/client-tcp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Logger } from '@nestjs/common';
2-
import * as JsonSocket from 'json-socket';
32
import * as net from 'net';
43
import { share, tap } from 'rxjs/operators';
54
import {
@@ -14,6 +13,7 @@ import {
1413
ClientOptions,
1514
TcpClientOptions,
1615
} from '../interfaces/client-metadata.interface';
16+
import { JsonSocket } from '../helpers/json-socket';
1717
import { ClientProxy } from './client-proxy';
1818
import { ECONNREFUSED } from './constants';
1919

@@ -42,7 +42,7 @@ export class ClientTCP extends ClientProxy {
4242
this.socket = this.createSocket();
4343
this.bindEvents(this.socket);
4444

45-
const source$ = this.connect$(this.socket._socket).pipe(
45+
const source$ = this.connect$(this.socket.netSocket).pipe(
4646
tap(() => {
4747
this.isConnected = true;
4848
this.socket.on(MESSAGE_EVENT, (buffer: WritePacket & PacketId) =>

packages/microservices/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const RQM_DEFAULT_URL = 'amqp://localhost';
99
export const CONNECT_EVENT = 'connect';
1010
export const DISCONNECT_EVENT = 'disconnect';
1111
export const MESSAGE_EVENT = 'message';
12+
export const DATA_EVENT = 'data';
1213
export const ERROR_EVENT = 'error';
1314
export const CLOSE_EVENT = 'close';
1415
export const SUBSCRIBE = 'subscribe';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class CorruptedPacketLengthException extends Error {
2+
constructor(rawContentLength: string) {
3+
super(`Corrupted length value "${rawContentLength}" supplied in a packet`);
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class InvalidJSONFormatException extends Error {
2+
constructor(err: Error, data: string) {
3+
super(`Could not parse JSON: ${err.message}\nRequest data: ${data}`);
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class NetSocketClosedException extends Error {
2+
constructor() {
3+
super(`The net socket is closed.`);
4+
}
5+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { Socket } from 'net';
2+
import { StringDecoder } from 'string_decoder';
3+
import {
4+
CLOSE_EVENT,
5+
CONNECT_EVENT,
6+
DATA_EVENT,
7+
ERROR_EVENT,
8+
MESSAGE_EVENT,
9+
} from '../constants';
10+
import { CorruptedPacketLengthException } from '../errors/corrupted-packet-length.exception';
11+
import { InvalidJSONFormatException } from '../errors/invalid-json-format.exception';
12+
import { NetSocketClosedException } from '../errors/net-socket-closed.exception';
13+
14+
export class JsonSocket {
15+
private contentLength: number | null = null;
16+
private isClosed = false;
17+
private buffer = '';
18+
19+
private readonly stringDecoder = new StringDecoder();
20+
private readonly delimeter = '#';
21+
22+
public get netSocket() {
23+
return this.socket;
24+
}
25+
26+
constructor(public readonly socket: Socket) {
27+
this.socket.on(DATA_EVENT, this.onData.bind(this));
28+
this.socket.on(CONNECT_EVENT, () => (this.isClosed = false));
29+
this.socket.on(CLOSE_EVENT, () => (this.isClosed = true));
30+
this.socket.on(ERROR_EVENT, () => (this.isClosed = true));
31+
}
32+
33+
public connect(port: number, host: string) {
34+
this.socket.connect(port, host);
35+
return this;
36+
}
37+
38+
public on(event: string, callback: (err?: any) => void) {
39+
this.socket.on(event, callback);
40+
return this;
41+
}
42+
43+
public once(event: string, callback: (err?: any) => void) {
44+
this.socket.once(event, callback);
45+
return this;
46+
}
47+
48+
public end() {
49+
this.socket.end();
50+
return this;
51+
}
52+
53+
public sendMessage(message: any, callback?: (err?: any) => void) {
54+
if (this.isClosed) {
55+
callback && callback(new NetSocketClosedException());
56+
return;
57+
}
58+
this.socket.write(this.formatMessageData(message), 'utf-8', callback);
59+
}
60+
61+
private onData(dataRaw: Buffer | string) {
62+
const data = Buffer.isBuffer(dataRaw)
63+
? this.stringDecoder.write(dataRaw)
64+
: dataRaw;
65+
66+
try {
67+
this.handleData(data);
68+
} catch (e) {
69+
this.socket.emit(ERROR_EVENT, e.message);
70+
this.socket.end();
71+
}
72+
}
73+
74+
private handleData(data: string) {
75+
this.buffer += data;
76+
77+
if (this.contentLength == null) {
78+
const i = this.buffer.indexOf(this.delimeter);
79+
/**
80+
* Check if the buffer has the delimeter (#),
81+
* if not, the end of the buffer string might be in the middle of a content length string
82+
*/
83+
if (i !== -1) {
84+
const rawContentLength = this.buffer.substring(0, i);
85+
this.contentLength = parseInt(rawContentLength, 10);
86+
87+
if (isNaN(this.contentLength)) {
88+
this.contentLength = null;
89+
this.buffer = '';
90+
throw new CorruptedPacketLengthException(rawContentLength);
91+
}
92+
this.buffer = this.buffer.substring(i + 1);
93+
}
94+
}
95+
96+
if (this.contentLength !== null) {
97+
const length = this.buffer.length;
98+
99+
if (length === this.contentLength) {
100+
this.handleMessage(this.buffer);
101+
} else if (length > this.contentLength) {
102+
const message = this.buffer.substring(0, this.contentLength);
103+
const rest = this.buffer.substring(this.contentLength);
104+
this.handleMessage(message);
105+
this.onData(rest);
106+
}
107+
}
108+
}
109+
110+
private handleMessage(data: string) {
111+
this.contentLength = null;
112+
this.buffer = '';
113+
114+
let message: Record<string, unknown>;
115+
try {
116+
message = JSON.parse(data);
117+
} catch (e) {
118+
throw new InvalidJSONFormatException(e, data);
119+
}
120+
message = message || {};
121+
this.socket.emit(MESSAGE_EVENT, message);
122+
}
123+
124+
private formatMessageData(message: any) {
125+
const messageData = JSON.stringify(message);
126+
const length = messageData.length;
127+
const data = length + this.delimeter + messageData;
128+
return data;
129+
}
130+
}

packages/microservices/server/server-tcp.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { isString, isUndefined } from '@nestjs/common/utils/shared.utils';
2-
import * as JsonSocket from 'json-socket';
32
import * as net from 'net';
4-
import { Server as NetSocket } from 'net';
3+
import { Server as NetSocket, Socket } from 'net';
54
import { Observable } from 'rxjs';
65
import {
76
CLOSE_EVENT,
@@ -15,6 +14,7 @@ import {
1514
MicroserviceOptions,
1615
TcpOptions,
1716
} from '../interfaces/microservice-configuration.interface';
17+
import { JsonSocket } from '../helpers/json-socket';
1818
import { Server } from './server';
1919

2020
export class ServerTCP extends Server implements CustomTransportStrategy {
@@ -39,15 +39,16 @@ export class ServerTCP extends Server implements CustomTransportStrategy {
3939
this.server.close();
4040
}
4141

42-
public bindHandler<T extends Record<string, any>>(socket: T) {
42+
public bindHandler(socket: Socket) {
4343
const readSocket = this.getSocketInstance(socket);
4444
readSocket.on(MESSAGE_EVENT, async (msg: ReadPacket & PacketId) =>
4545
this.handleMessage(readSocket, msg),
4646
);
47+
readSocket.on(ERROR_EVENT, this.handleError.bind(this));
4748
}
4849

49-
public async handleMessage<T extends Record<string, any>>(
50-
socket: T,
50+
public async handleMessage(
51+
socket: JsonSocket,
5152
packet: ReadPacket & PacketId,
5253
) {
5354
const pattern = !isString(packet.pattern)
@@ -98,7 +99,7 @@ export class ServerTCP extends Server implements CustomTransportStrategy {
9899
this.server.on(CLOSE_EVENT, this.handleClose.bind(this));
99100
}
100101

101-
private getSocketInstance<T>(socket: T): JsonSocket {
102+
private getSocketInstance(socket: Socket): JsonSocket {
102103
return new JsonSocket(socket);
103104
}
104105
}

packages/microservices/test/client/client-tcp.spec.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,7 @@ import { ERROR_EVENT } from '../../constants';
66

77
describe('ClientTCP', () => {
88
let client: ClientTCP;
9-
let socket: {
10-
connect: sinon.SinonStub;
11-
publish: sinon.SinonSpy;
12-
_socket: {
13-
addListener: sinon.SinonStub;
14-
removeListener: sinon.SinonSpy;
15-
once: sinon.SinonStub;
16-
};
17-
on: sinon.SinonStub;
18-
end: sinon.SinonSpy;
19-
sendMessage: sinon.SinonSpy;
20-
};
9+
let socket;
2110
let createSocketStub: sinon.SinonStub;
2211

2312
beforeEach(() => {
@@ -27,9 +16,8 @@ describe('ClientTCP', () => {
2716

2817
socket = {
2918
connect: sinon.stub(),
30-
publish: sinon.spy(),
3119
on: sinon.stub().callsFake(onFakeCallback),
32-
_socket: {
20+
netSocket: {
3321
addListener: sinon.stub().callsFake(onFakeCallback),
3422
removeListener: sinon.spy(),
3523
once: sinon.stub().callsFake(onFakeCallback),
@@ -134,7 +122,9 @@ describe('ClientTCP', () => {
134122
toPromise: () => source,
135123
pipe: () => source,
136124
};
137-
connect$Stub = sinon.stub(client, 'connect$' as any).callsFake(() => source);
125+
connect$Stub = sinon
126+
.stub(client, 'connect$' as any)
127+
.callsFake(() => source);
138128
await client.connect();
139129
});
140130
afterEach(() => {

0 commit comments

Comments
 (0)