@encharm/cws 4.8.0 → 4.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -0
- package/dist/bindings/cws_darwin_arm64_node115.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node127.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node137.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node147.node +0 -0
- package/dist/bindings/cws_linux_arm64_node115.node +0 -0
- package/dist/bindings/cws_linux_arm64_node127.node +0 -0
- package/dist/bindings/cws_linux_arm64_node137.node +0 -0
- package/dist/bindings/cws_linux_arm64_node147.node +0 -0
- package/dist/bindings/cws_linux_x64_node115.node +0 -0
- package/dist/bindings/cws_linux_x64_node127.node +0 -0
- package/dist/bindings/cws_linux_x64_node137.node +0 -0
- package/dist/bindings/cws_linux_x64_node147.node +0 -0
- package/dist/bindings/cws_win32_x64_node115.node +0 -0
- package/dist/bindings/cws_win32_x64_node127.node +0 -0
- package/dist/bindings/cws_win32_x64_node137.node +0 -0
- package/dist/bindings/cws_win32_x64_node147.node +0 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +6 -4
- package/package.json +1 -1
- package/src/HTTPSocket.cpp +1 -1
- package/src/Socket.h +37 -7
- package/src/WebSocket.cpp +12 -3
- package/src/Zlib.cpp +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## Released 4.8.3
|
|
2
|
+
* Corked sends up to 1 KB use the per-loop block pool instead of malloc/free per message; queued messages remember their pool block. Also initialise the `reserved` callback argument, which was passed uninitialised for messages that hit the write queue.
|
|
3
|
+
* JS: resolve the native namespace once per socket instead of on every `send`/`close`/`ping`.
|
|
4
|
+
|
|
5
|
+
## Released 4.8.2
|
|
6
|
+
* Accept compressed messages that end with a BFINAL=1 DEFLATE block (RFC 7692 section 7.2.3.4). Such clients (libdeflate-based and some non-zlib implementations) were disconnected on their first compressed message.
|
|
7
|
+
|
|
8
|
+
## Released 4.8.1
|
|
9
|
+
* Skip the kernel-level cork/uncork (two `setsockopt` calls) around every socket read when write corking is active; those writes leave in the end-of-tick gathered write anyway. Saves 1-2 us of server CPU per read.
|
|
10
|
+
|
|
1
11
|
## Released 4.8.0
|
|
2
12
|
* Prebuilt bindings now compress with zlib-ng 2.2.4 (vendored in `deps/zlib-ng`, statically linked, native `zng_` API so it cannot collide with Node's zlib). Measured on a real RPC stream: level 1 costs 2.2-2.5 us/KB vs 5.6-8 us/KB with zlib. The node-gyp fallback build keeps using Node's zlib through the same `src/Zlib.cpp` wrapper.
|
|
3
13
|
* New `perMessageDeflate.level` option (default 2) and a `zlibBackend` export naming the compiled-in implementation.
|
package/README.md
CHANGED
|
@@ -14,6 +14,9 @@ This table is true if you run ssl directly with `cws` (`Node.js`). In case if yo
|
|
|
14
14
|
|
|
15
15
|
| cWS Version | Node 26 | Node 24 | Node 22 | Node 20 |
|
|
16
16
|
|-------------|---------|----------|---------|----------
|
|
17
|
+
| 4.8.3 | X | X | X | X |
|
|
18
|
+
| 4.8.2 | X | X | X | X |
|
|
19
|
+
| 4.8.1 | X | X | X | X |
|
|
17
20
|
| 4.8.0 | X | X | X | X |
|
|
18
21
|
| 4.7.0 | X | X | X | X |
|
|
19
22
|
| 4.6.0 | X | X | X | X |
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/client.d.ts
CHANGED
package/dist/client.js
CHANGED
|
@@ -26,8 +26,10 @@ class WebSocket {
|
|
|
26
26
|
message: shared_1.noop
|
|
27
27
|
};
|
|
28
28
|
this.socketType = 'client';
|
|
29
|
+
this.nativeApi = shared_1.native.client;
|
|
29
30
|
if (!this.url && this.options.external) {
|
|
30
31
|
this.socketType = 'server';
|
|
32
|
+
this.nativeApi = shared_1.native.server;
|
|
31
33
|
this.external = this.options.external;
|
|
32
34
|
this.compressThreshold = this.options.compressThreshold;
|
|
33
35
|
}
|
|
@@ -90,7 +92,7 @@ class WebSocket {
|
|
|
90
92
|
else if (this.compressThreshold !== undefined) {
|
|
91
93
|
compress = messageByteLength(message) >= this.compressThreshold;
|
|
92
94
|
}
|
|
93
|
-
|
|
95
|
+
this.nativeApi.send(this.external, message, opCode, cb ? () => process.nextTick(cb) : null, compress);
|
|
94
96
|
}
|
|
95
97
|
else if (cb) {
|
|
96
98
|
cb(new Error('Socket not connected'));
|
|
@@ -98,18 +100,18 @@ class WebSocket {
|
|
|
98
100
|
}
|
|
99
101
|
ping(message) {
|
|
100
102
|
if (this.external) {
|
|
101
|
-
|
|
103
|
+
this.nativeApi.send(this.external, message, shared_1.OPCODE_PING);
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
close(code = 1000, reason) {
|
|
105
107
|
if (this.external) {
|
|
106
|
-
|
|
108
|
+
this.nativeApi.close(this.external, code, reason);
|
|
107
109
|
this.external = null;
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
112
|
terminate() {
|
|
111
113
|
if (this.external) {
|
|
112
|
-
|
|
114
|
+
this.nativeApi.terminate(this.external);
|
|
113
115
|
this.external = null;
|
|
114
116
|
}
|
|
115
117
|
}
|
package/package.json
CHANGED
package/src/HTTPSocket.cpp
CHANGED
|
@@ -289,7 +289,7 @@ void HttpSocket<isServer>::onEnd(cS::Socket *s) {
|
|
|
289
289
|
if (message->callback) {
|
|
290
290
|
message->callback(nullptr, message->callbackData, true, nullptr);
|
|
291
291
|
}
|
|
292
|
-
httpSocket->messageQueue.pop();
|
|
292
|
+
httpSocket->messageQueue.pop(httpSocket->nodeData);
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
while (httpSocket->outstandingResponsesHead) {
|
package/src/Socket.h
CHANGED
|
@@ -50,20 +50,32 @@ protected:
|
|
|
50
50
|
Message *nextMessage = nullptr;
|
|
51
51
|
void (*callback)(void *socket, void *data, bool cancelled, void *reserved) = nullptr;
|
|
52
52
|
void *callbackData = nullptr, *reserved = nullptr;
|
|
53
|
+
// >= 0: block came from NodeData's small-block pool (index for freeSmallMemoryBlock);
|
|
54
|
+
// -1: heap (new char[]). Messages are allocated as raw char arrays, so every
|
|
55
|
+
// allocation site must set this explicitly (the initializers above never run).
|
|
56
|
+
int poolIndex = -1;
|
|
53
57
|
};
|
|
54
58
|
|
|
55
59
|
Message *head = nullptr, *tail = nullptr;
|
|
56
60
|
size_t totalLength = 0;
|
|
57
61
|
|
|
58
|
-
void
|
|
62
|
+
static void release(NodeData *nodeData, Message *message) {
|
|
63
|
+
if (message->poolIndex >= 0) {
|
|
64
|
+
nodeData->freeSmallMemoryBlock((char *) message, message->poolIndex);
|
|
65
|
+
} else {
|
|
66
|
+
delete [] (char *) message;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void pop(NodeData *nodeData)
|
|
59
71
|
{
|
|
60
72
|
Message *nextMessage;
|
|
61
73
|
if ((nextMessage = head->nextMessage)) {
|
|
62
74
|
totalLength -= head->length;
|
|
63
|
-
|
|
75
|
+
release(nodeData, head);
|
|
64
76
|
head = nextMessage;
|
|
65
77
|
} else {
|
|
66
|
-
|
|
78
|
+
release(nodeData, head);
|
|
67
79
|
head = tail = nullptr;
|
|
68
80
|
totalLength = 0;
|
|
69
81
|
}
|
|
@@ -174,7 +186,7 @@ protected:
|
|
|
174
186
|
// which frees the queue (use-after-free otherwise).
|
|
175
187
|
auto callback = messagePtr->callback;
|
|
176
188
|
void *callbackData = messagePtr->callbackData, *reserved = messagePtr->reserved;
|
|
177
|
-
socket->messageQueue.pop();
|
|
189
|
+
socket->messageQueue.pop(socket->nodeData);
|
|
178
190
|
if (callback) {
|
|
179
191
|
callback(p, callbackData, false, reserved);
|
|
180
192
|
if (socket->isClosed()) {
|
|
@@ -256,7 +268,7 @@ protected:
|
|
|
256
268
|
// which frees the queue (use-after-free otherwise).
|
|
257
269
|
auto callback = messagePtr->callback;
|
|
258
270
|
void *callbackData = messagePtr->callbackData, *reserved = messagePtr->reserved;
|
|
259
|
-
socket->messageQueue.pop();
|
|
271
|
+
socket->messageQueue.pop(socket->nodeData);
|
|
260
272
|
if (callback) {
|
|
261
273
|
callback(p, callbackData, false, reserved);
|
|
262
274
|
if (socket->isClosed()) {
|
|
@@ -318,6 +330,10 @@ protected:
|
|
|
318
330
|
messagePtr->length = length;
|
|
319
331
|
messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
|
|
320
332
|
messagePtr->nextMessage = nullptr;
|
|
333
|
+
messagePtr->callback = nullptr;
|
|
334
|
+
messagePtr->callbackData = nullptr;
|
|
335
|
+
messagePtr->reserved = nullptr;
|
|
336
|
+
messagePtr->poolIndex = -1;
|
|
321
337
|
|
|
322
338
|
if (data) {
|
|
323
339
|
memcpy((char *) messagePtr->data, data, messagePtr->length);
|
|
@@ -385,10 +401,21 @@ protected:
|
|
|
385
401
|
if (corkActive()) {
|
|
386
402
|
// Frame now, write later: everything sent to this socket during the
|
|
387
403
|
// current loop iteration goes out in one gathered write (uncork()).
|
|
388
|
-
|
|
404
|
+
// Small messages come from the per-loop block pool (pop() returns them).
|
|
405
|
+
Queue::Message *messagePtr;
|
|
406
|
+
if (estimatedLength <= cS::NodeData::preAllocMaxSize) {
|
|
407
|
+
int memoryIndex = nodeData->getMemoryBlockIndex((int) estimatedLength);
|
|
408
|
+
messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
|
|
409
|
+
messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
|
|
410
|
+
messagePtr->poolIndex = memoryIndex;
|
|
411
|
+
} else {
|
|
412
|
+
messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message));
|
|
413
|
+
}
|
|
389
414
|
messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
|
|
415
|
+
messagePtr->nextMessage = nullptr;
|
|
390
416
|
messagePtr->callback = callback;
|
|
391
417
|
messagePtr->callbackData = callbackData;
|
|
418
|
+
messagePtr->reserved = nullptr;
|
|
392
419
|
enqueue(messagePtr);
|
|
393
420
|
if (!corkPending) {
|
|
394
421
|
corkPending = true;
|
|
@@ -405,6 +432,9 @@ protected:
|
|
|
405
432
|
Queue::Message *messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
|
|
406
433
|
messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
|
|
407
434
|
messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
|
|
435
|
+
messagePtr->nextMessage = nullptr;
|
|
436
|
+
messagePtr->reserved = nullptr;
|
|
437
|
+
messagePtr->poolIndex = memoryIndex;
|
|
408
438
|
|
|
409
439
|
bool wasTransferred;
|
|
410
440
|
if (write(messagePtr, wasTransferred)) {
|
|
@@ -589,7 +619,7 @@ public:
|
|
|
589
619
|
if (callbacks && m->callback) {
|
|
590
620
|
callbacks->push_back({m->callback, m->callbackData, m->reserved});
|
|
591
621
|
}
|
|
592
|
-
messageQueue.pop();
|
|
622
|
+
messageQueue.pop(nodeData);
|
|
593
623
|
} else {
|
|
594
624
|
m->length -= sent;
|
|
595
625
|
m->data += sent;
|
package/src/WebSocket.cpp
CHANGED
|
@@ -151,6 +151,9 @@ void WebSocket<isServer>::sendPrepared(typename WebSocket<isServer>::PreparedMes
|
|
|
151
151
|
Queue::Message *messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
|
|
152
152
|
messagePtr->data = preparedMessage->buffer;
|
|
153
153
|
messagePtr->length = preparedMessage->length;
|
|
154
|
+
messagePtr->nextMessage = nullptr;
|
|
155
|
+
messagePtr->reserved = nullptr;
|
|
156
|
+
messagePtr->poolIndex = memoryIndex;
|
|
154
157
|
|
|
155
158
|
bool wasTransferred;
|
|
156
159
|
if (write(messagePtr, wasTransferred)) {
|
|
@@ -195,9 +198,15 @@ cS::Socket *WebSocket<isServer>::onData(cS::Socket *s, char *data, size_t length
|
|
|
195
198
|
|
|
196
199
|
webSocket->hasOutstandingPong = false;
|
|
197
200
|
if (!webSocket->isShuttingDown()) {
|
|
198
|
-
|
|
201
|
+
// The kernel-level cork (TCP_CORK/TCP_NOPUSH) around a read only helps when
|
|
202
|
+
// replies are written immediately; with write corking they leave in one gathered
|
|
203
|
+
// write at the end of the tick, so skip the two setsockopt calls per read.
|
|
204
|
+
bool kernelCork = !webSocket->corkActive();
|
|
205
|
+
if (kernelCork) {
|
|
206
|
+
webSocket->cork(true);
|
|
207
|
+
}
|
|
199
208
|
WebSocketProtocol<isServer, WebSocket<isServer>>::consume(data, (unsigned int) length, webSocket);
|
|
200
|
-
if (!webSocket->isClosed()) {
|
|
209
|
+
if (kernelCork && !webSocket->isClosed()) {
|
|
201
210
|
webSocket->cork(false);
|
|
202
211
|
}
|
|
203
212
|
}
|
|
@@ -306,7 +315,7 @@ void WebSocket<isServer>::onEnd(cS::Socket *s) {
|
|
|
306
315
|
if (message->callback) {
|
|
307
316
|
message->callback(nullptr, message->callbackData, true, nullptr);
|
|
308
317
|
}
|
|
309
|
-
webSocket->messageQueue.pop();
|
|
318
|
+
webSocket->messageQueue.pop(webSocket->nodeData);
|
|
310
319
|
}
|
|
311
320
|
|
|
312
321
|
webSocket->nodeData->clearPendingPollChanges(webSocket);
|
package/src/Zlib.cpp
CHANGED
|
@@ -106,7 +106,10 @@ char *inflate(Stream *stream, char *data, size_t &length, size_t maxPayload, cha
|
|
|
106
106
|
zs.next_out = (OutPtr) buffer;
|
|
107
107
|
zs.avail_out = (unsigned int) bufferSize;
|
|
108
108
|
err = ZFN(inflate)(&zs, Z_FINISH);
|
|
109
|
-
|
|
109
|
+
// Z_STREAM_END: the sender ended the message with a BFINAL=1 block, which
|
|
110
|
+
// RFC 7692 section 7.2.3.4 permits (libdeflate and some non-zlib clients do
|
|
111
|
+
// this); anything left in the input is the 4-byte tail and is ignored.
|
|
112
|
+
if (!zs.avail_in || err == Z_STREAM_END) {
|
|
110
113
|
break;
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -115,7 +118,7 @@ char *inflate(Stream *stream, char *data, size_t &length, size_t maxPayload, cha
|
|
|
115
118
|
|
|
116
119
|
ZFN(inflateReset)(&zs);
|
|
117
120
|
|
|
118
|
-
if ((err != Z_BUF_ERROR && err != Z_OK) || dynamic.length() > maxPayload) {
|
|
121
|
+
if ((err != Z_BUF_ERROR && err != Z_OK && err != Z_STREAM_END) || dynamic.length() > maxPayload) {
|
|
119
122
|
length = 0;
|
|
120
123
|
return nullptr;
|
|
121
124
|
}
|