iodine 0.2.12 → 0.2.13
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +9 -1
- data/README.md +10 -9
- data/bin/ws-echo +2 -0
- data/ext/iodine/websockets.c +91 -74
- data/ext/iodine/websockets.h +5 -3
- data/iodine.gemspec +2 -2
- data/lib/iodine/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc27a8990d89bbb19294133f6ec8178d2a586ea2
|
4
|
+
data.tar.gz: 8e9ec06545307abd666902d434438679e5bdd7ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a25fe9b7584dbeba407d55c1f150fdfab048fb7892a46ec32777d55b38ab3742faf0f7a972c118d3d6f26804380825324ff6f4b2665f91c56b0cb44f7f6c5ae6
|
7
|
+
data.tar.gz: b0e1ca2d60baae702e1f953cd6b9e13566168ae4fe989e1d31e6e0fd37cbbfc8bf3c3dfcf1e4e83049e09c45ca369c9efd08779965562b17cd576308925573e7
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -8,9 +8,17 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
8
8
|
|
9
9
|
***
|
10
10
|
|
11
|
+
Change log v.0.2.13
|
12
|
+
|
13
|
+
**Fix**: Fixed an issue presented in the C layer, where big fragmented websocket messages sent by the client could cause parsing errors and potentially, in some cases, cause a server thread to spin in a loop (DoS). Credit to @Filly for exposing the issue in the [`facil.io`](https://github.com/boazsegev/facil.io) layer. It should be noted that Chrome is the only browser where this issue could be invoked for testing.
|
14
|
+
|
15
|
+
**Credit**: credit to Elia Schito (@elia) and Augusts Bautra (@Epigene) for fixing parts of the documentation (PR #11 , #12).
|
16
|
+
|
17
|
+
***
|
18
|
+
|
11
19
|
Change log v.0.2.12
|
12
20
|
|
13
|
-
**Fix
|
21
|
+
**Fix**: removed `mempool` after it failed some stress and concurrency tests.
|
14
22
|
|
15
23
|
***
|
16
24
|
|
data/README.md
CHANGED
@@ -45,9 +45,10 @@ Puma's model of 16 threads and 4 processes is easily adopted and proved to provi
|
|
45
45
|
bundler exec iodine -p $PORT -t 16 -w 4
|
46
46
|
```
|
47
47
|
|
48
|
-
It should be noted that automatic process scaling will cause issues with Websocket broadcast (`each`) support,
|
48
|
+
It should be noted that automatic process scaling will cause issues with Websocket broadcast (`each`) support, since the `Websocket#each` method will be limited to the calling process (other clients might be connected to a different process).
|
49
49
|
|
50
|
-
|
50
|
+
It is recommended that you consider using Redis to scale Websocket "events" across processes / machines.
|
51
|
+
Look into [plezi.io](http://www.plezi.io) for automatic Websocket scaling with Redis and Iodine.
|
51
52
|
|
52
53
|
### Writing data to the network layer
|
53
54
|
|
@@ -55,7 +56,7 @@ Iodine allows Ruby to write strings to the network layer. This includes HTTP and
|
|
55
56
|
|
56
57
|
Iodine will handle an internal buffer (~4 to ~16 Mb, version depending) so that `write` can return immediately (non-blocking).
|
57
58
|
|
58
|
-
However, when the buffer is full, `write` will block until enough space in
|
59
|
+
However, when the buffer is full, `write` will block until enough space in the buffer becomes available. Sending up to 16Kb of data (a single buffer "packet") is optimal. Sending a larger response might effect concurrency. Best Websocket response length is ~1Kb (1 TCP / IP packet) and allows for faster transmissions.
|
59
60
|
|
60
61
|
When using the Iodine's web server (`Iodine::Rack`), the static file service offered by Iodine streams files (instead of using the buffer). Every file response will require up to 2 buffer "packets" (~32Kb), one for the header and the other for file streaming.
|
61
62
|
|
@@ -155,7 +156,7 @@ Iodine.start
|
|
155
156
|
|
156
157
|
#### TCP/IP (raw) sockets
|
157
158
|
|
158
|
-
Upgrading to a custom protocol (i.e., in order to implement your own Websocket protocol with special extensions) is performed almost the
|
159
|
+
Upgrading to a custom protocol (i.e., in order to implement your own Websocket protocol with special extensions) is performed almost the same way, using `env['upgrade.tcp']`. In the following (terminal) example, we'll use an echo server without direct socket echo:
|
159
160
|
|
160
161
|
```ruby
|
161
162
|
require 'iodine'
|
@@ -180,7 +181,7 @@ Iodine.start
|
|
180
181
|
|
181
182
|
#### A few notes
|
182
183
|
|
183
|
-
This design has a number of benefits, some of them related to better IO handling, resource optimization (no need for two IO polling systems) etc
|
184
|
+
This design has a number of benefits, some of them related to better IO handling, resource optimization (no need for two IO polling systems), etc. This also allows us to use middleware without interfering with connection upgrades and provides backwards compatibility.
|
184
185
|
|
185
186
|
Iodine::Rack imposes a few restrictions for performance and security reasons, such as that the headers (both sending and receiving) must be less than 8Kb in size. These restrictions shouldn't be an issue and are similar to limitations imposed by Apache.
|
186
187
|
|
@@ -270,7 +271,7 @@ Review the `iodine -?` help for more data.
|
|
270
271
|
|
271
272
|
Remember to compare the memory footprint after running some requests - it's not just speed that C is helping with, it's also memory management and object pooling (i.e., Iodine uses a buffer packet pool management).
|
272
273
|
|
273
|
-
## Can I try before
|
274
|
+
## Can I try before I buy?
|
274
275
|
|
275
276
|
Well, it is **free** and **open source**, no need to buy.. and of course you can try it out.
|
276
277
|
|
@@ -280,9 +281,9 @@ It's installable just like any other gem on MRI, run:
|
|
280
281
|
$ gem install iodine
|
281
282
|
```
|
282
283
|
|
283
|
-
If building the native C extension fails, please
|
284
|
+
If building the native C extension fails, please note that some Ruby installations, such as on Ubuntu, require that you separately install the development headers (`ruby.h` and friends). I have no idea why they do that, as you will need the development headers for any native gems you want to install - so hurry up and get them.
|
284
285
|
|
285
|
-
If you have the development headers but still can't compile the Iodine extension, [open an issue](https://github.com/boazsegev/iodine/issues) with any messages you're getting and I be happy to look into it.
|
286
|
+
If you have the development headers but still can't compile the Iodine extension, [open an issue](https://github.com/boazsegev/iodine/issues) with any messages you're getting and I'll be happy to look into it.
|
286
287
|
|
287
288
|
## Mr. Sandman, write me a server
|
288
289
|
|
@@ -370,7 +371,7 @@ Here's a few things you can use from this project and they seem to be handy to h
|
|
370
371
|
|
371
372
|
Some people use global Ruby arrays, adding and removing Ruby objects to the array, but that sounds like a performance hog to me.
|
372
373
|
|
373
|
-
This one is a simple binary tree with a Ruby GC callback. Remember to initialize the Registry (`Registry.init(owner)`) so it's "owned" by some
|
374
|
+
This one is a simple binary tree with a Ruby GC callback. Remember to initialize the Registry (`Registry.init(owner)`) so it's "owned" by some Ruby-land object, this allows it to bridge the two worlds for the GC's mark and sweep.
|
374
375
|
|
375
376
|
I'm attaching it to one of Iodine's library classes, just in-case someone adopts my code and decides the registry should be owned by the global Object class.
|
376
377
|
|
data/bin/ws-echo
CHANGED
@@ -111,3 +111,5 @@ Iodine.start
|
|
111
111
|
# ws.onclose = function(e) {console.log("closed")};
|
112
112
|
# ws.onopen = function(e) {e.target.send("hi");};
|
113
113
|
# };
|
114
|
+
# ws = new WebSocket("ws://localhost:3000"); ws.onmessage = function(e) {console.log("Got message " + e.data.length + " bytes long"); for(var i = 0; i < e.data.length ; i += 4) {if (e.data.slice(i, i+4) != "text") {console.log( "incoming message corrupted? ", e.data.slice(i, i+4) ); return;};}; }; ws.onclose = function(e) {console.log("closed")}; ws.onopen = function(e) {ws.send("hi");};
|
115
|
+
# str = "text"; function size_test() { if(ws.readyState != 1) return; if(str.length > 4194304) {console.log("str reached 4MiB!"); str = "test"; return;}; ws.send(str); str = str + str; window.setTimeout(size_test, 150); }; size_test();
|
data/ext/iodine/websockets.c
CHANGED
@@ -41,7 +41,7 @@ struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s);
|
|
41
41
|
void free_ws_buffer(ws_s *owner, struct buffer_s);
|
42
42
|
|
43
43
|
/** Sets the initial buffer size. (4Kb)*/
|
44
|
-
#define WS_INITIAL_BUFFER_SIZE
|
44
|
+
#define WS_INITIAL_BUFFER_SIZE 4096UL
|
45
45
|
|
46
46
|
/*******************************************************************************
|
47
47
|
Buffer management - simple implementation...
|
@@ -65,6 +65,7 @@ struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s buff) {
|
|
65
65
|
void *tmp = realloc(buff.data, buff.size);
|
66
66
|
if (!tmp) {
|
67
67
|
free_ws_buffer(owner, buff);
|
68
|
+
buff.data = NULL;
|
68
69
|
buff.size = 0;
|
69
70
|
}
|
70
71
|
buff.data = tmp;
|
@@ -110,12 +111,11 @@ struct Websocket {
|
|
110
111
|
struct {
|
111
112
|
union {
|
112
113
|
unsigned len1 : 16;
|
113
|
-
unsigned long len2 : 64;
|
114
|
+
unsigned long long len2 : 64;
|
114
115
|
char bytes[8];
|
115
116
|
} psize;
|
116
117
|
size_t length;
|
117
118
|
size_t received;
|
118
|
-
int data_len;
|
119
119
|
char mask[4];
|
120
120
|
struct {
|
121
121
|
unsigned op_code : 4;
|
@@ -133,8 +133,9 @@ struct Websocket {
|
|
133
133
|
unsigned at_mask : 2;
|
134
134
|
unsigned has_len : 1;
|
135
135
|
unsigned at_len : 3;
|
136
|
-
unsigned
|
136
|
+
unsigned has_head : 1;
|
137
137
|
} state;
|
138
|
+
unsigned client : 1;
|
138
139
|
} parser;
|
139
140
|
};
|
140
141
|
|
@@ -146,9 +147,10 @@ char *WEBSOCKET_ID_STR = "websockets";
|
|
146
147
|
/**
|
147
148
|
A thread localized buffer used for reading and parsing data from the socket.
|
148
149
|
*/
|
150
|
+
#define WEBSOCKET_READ_MAX 4096
|
149
151
|
static __thread struct {
|
150
152
|
int pos;
|
151
|
-
char buffer[
|
153
|
+
char buffer[WEBSOCKET_READ_MAX];
|
152
154
|
} read_buffer;
|
153
155
|
|
154
156
|
/*******************************************************************************
|
@@ -200,12 +202,15 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
200
202
|
if (ws == NULL || ws->protocol.service != WEBSOCKET_ID_STR)
|
201
203
|
return;
|
202
204
|
ssize_t len = 0;
|
203
|
-
|
204
|
-
|
205
|
+
ssize_t data_len = 0;
|
206
|
+
while ((len = sock_read(sockfd, read_buffer.buffer, WEBSOCKET_READ_MAX)) >
|
207
|
+
0) {
|
208
|
+
data_len = 0;
|
205
209
|
read_buffer.pos = 0;
|
206
210
|
while (read_buffer.pos < len) {
|
207
211
|
// collect the frame's head
|
208
|
-
if (!
|
212
|
+
if (!ws->parser.state.has_head) {
|
213
|
+
ws->parser.state.has_head = 1;
|
209
214
|
*((char *)(&(ws->parser.head))) = read_buffer.buffer[read_buffer.pos];
|
210
215
|
// save a copy if it's the first head in a fragmented message
|
211
216
|
if (!(*(char *)(&ws->parser.head2))) {
|
@@ -218,18 +223,24 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
218
223
|
}
|
219
224
|
|
220
225
|
// save the mask and size information
|
221
|
-
if (!
|
226
|
+
if (!ws->parser.state.at_len && !ws->parser.state.has_len) {
|
227
|
+
// uint8_t tmp = ws->parser.sdata.masked;
|
222
228
|
*((char *)(&(ws->parser.sdata))) = read_buffer.buffer[read_buffer.pos];
|
229
|
+
// ws->parser.sdata.masked |= tmp;
|
223
230
|
// set length
|
224
|
-
ws->parser.state.at_len = ws->parser.sdata.size == 127
|
225
|
-
|
226
|
-
|
231
|
+
ws->parser.state.at_len = (ws->parser.sdata.size == 127
|
232
|
+
? 7
|
233
|
+
: ws->parser.sdata.size == 126 ? 1 : 0);
|
234
|
+
if (!ws->parser.state.at_len) {
|
235
|
+
ws->parser.length = ws->parser.sdata.size;
|
236
|
+
ws->parser.state.has_len = 1;
|
237
|
+
}
|
227
238
|
read_buffer.pos++;
|
228
239
|
continue;
|
229
240
|
}
|
230
241
|
|
231
242
|
// check that if we need to collect the length data
|
232
|
-
if (
|
243
|
+
if (!ws->parser.state.has_len) {
|
233
244
|
// avoiding a loop so we don't mixup the meaning of "continue" and
|
234
245
|
// "break"
|
235
246
|
collect_len:
|
@@ -264,10 +275,16 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
264
275
|
goto collect_len;
|
265
276
|
}
|
266
277
|
#endif
|
278
|
+
// check message size limit
|
279
|
+
if (ws->max_msg_size <
|
280
|
+
ws->length + (ws->parser.length - ws->parser.received)) {
|
281
|
+
// close connection!
|
282
|
+
fprintf(stderr, "ERROR Websocket: Payload too big, review limits.\n");
|
283
|
+
sock_close(sockfd);
|
284
|
+
return;
|
285
|
+
}
|
267
286
|
continue;
|
268
|
-
}
|
269
|
-
// we should have the length data in the head
|
270
|
-
ws->parser.length = ws->parser.sdata.size;
|
287
|
+
}
|
271
288
|
|
272
289
|
// check that the data is masked and that we didn't colleced the mask yet
|
273
290
|
if (ws->parser.sdata.masked && !(ws->parser.state.has_mask)) {
|
@@ -294,35 +311,29 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
294
311
|
// Now that we know everything about the frame, let's collect the data
|
295
312
|
|
296
313
|
// How much data in the buffer is part of the frame?
|
297
|
-
|
298
|
-
if (
|
299
|
-
|
314
|
+
data_len = len - read_buffer.pos;
|
315
|
+
if (data_len + ws->parser.received > ws->parser.length)
|
316
|
+
data_len = ws->parser.length - ws->parser.received;
|
300
317
|
|
301
318
|
// a note about unmasking: since ws->parser.state.at_mask is only 2 bits,
|
302
319
|
// it will wrap around (i.e. 3++ == 0), so no modulus is required :-)
|
303
320
|
// unmask:
|
304
321
|
if (ws->parser.sdata.masked) {
|
305
|
-
for (int i = 0; i <
|
322
|
+
for (int i = 0; i < data_len; i++) {
|
306
323
|
read_buffer.buffer[i + read_buffer.pos] ^=
|
307
324
|
ws->parser.mask[ws->parser.state.at_mask++];
|
308
325
|
}
|
309
|
-
} else if (ws->parser.
|
326
|
+
} else if (ws->parser.client == 0) {
|
310
327
|
// enforce masking unless acting as client, also for security reasons...
|
311
328
|
fprintf(stderr, "ERROR Websockets: unmasked frame, disconnecting.\n");
|
312
329
|
sock_close(sockfd);
|
313
330
|
return;
|
314
331
|
}
|
315
332
|
// Copy the data to the Websocket buffer - only if it's a user message
|
316
|
-
if (
|
317
|
-
(
|
318
|
-
(ws->parser.
|
319
|
-
|
320
|
-
if (ws->max_msg_size < ws->length + ws->parser.data_len) {
|
321
|
-
// close connection!
|
322
|
-
fprintf(stderr, "ERROR Websocket: Payload too big, review limits.\n");
|
323
|
-
sock_close(sockfd);
|
324
|
-
return;
|
325
|
-
}
|
333
|
+
if (data_len &&
|
334
|
+
(ws->parser.head.op_code == 1 || ws->parser.head.op_code == 2 ||
|
335
|
+
(!ws->parser.head.op_code && (ws->parser.head2.op_code == 1 ||
|
336
|
+
ws->parser.head2.op_code == 2)))) {
|
326
337
|
// review and resize the buffer's capacity - it can only grow.
|
327
338
|
if (ws->length + ws->parser.length - ws->parser.received >
|
328
339
|
ws->buffer.size) {
|
@@ -335,42 +346,37 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
335
346
|
}
|
336
347
|
// copy here
|
337
348
|
memcpy((uint8_t *)ws->buffer.data + ws->length,
|
338
|
-
read_buffer.buffer + read_buffer.pos,
|
339
|
-
ws->length +=
|
349
|
+
read_buffer.buffer + read_buffer.pos, data_len);
|
350
|
+
ws->length += data_len;
|
340
351
|
}
|
341
352
|
// set the frame's data received so far (copied or not)
|
342
|
-
|
343
|
-
// Ruby buffer capacity (within the GVL resize function).
|
344
|
-
ws->parser.received += ws->parser.data_len;
|
353
|
+
ws->parser.received += data_len;
|
345
354
|
|
346
355
|
// check that we have collected the whole of the frame.
|
347
356
|
if (ws->parser.length > ws->parser.received) {
|
348
|
-
read_buffer.pos +=
|
357
|
+
read_buffer.pos += data_len;
|
358
|
+
// fprintf(stderr, "%p websocket has %lu out of %lu\n", (void *)ws,
|
359
|
+
// ws->parser.received, ws->parser.length);
|
349
360
|
continue;
|
350
361
|
}
|
351
362
|
|
352
363
|
// we have the whole frame, time to process the data.
|
353
|
-
// pings, pongs and other non-
|
364
|
+
// pings, pongs and other non-user messages are handled independently.
|
354
365
|
if (ws->parser.head.op_code == 0 || ws->parser.head.op_code == 1 ||
|
355
366
|
ws->parser.head.op_code == 2) {
|
356
|
-
/* a user data frame
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
} else if (ws->parser.head2.op_code == 2) {
|
362
|
-
/* binary data */
|
363
|
-
} else // not a recognized frame, don't act
|
364
|
-
goto reset_parser;
|
365
|
-
// call the on_message callback
|
366
|
-
|
367
|
-
if (ws->on_message)
|
368
|
-
ws->on_message(ws, ws->buffer.data, ws->length,
|
369
|
-
ws->parser.head2.op_code == 1);
|
370
|
-
goto reset_parser;
|
367
|
+
/* a user data frame - make sure we got the `fin` flag, or an error
|
368
|
+
* occured */
|
369
|
+
if (!ws->parser.head.fin) {
|
370
|
+
/* This frame was a partial message. */
|
371
|
+
goto reset_state;
|
371
372
|
}
|
373
|
+
/* This was the last frame */
|
374
|
+
if (ws->on_message) /* call the on_message callback */
|
375
|
+
ws->on_message(ws, ws->buffer.data, ws->length,
|
376
|
+
ws->parser.head2.op_code == 1);
|
377
|
+
goto reset_parser;
|
372
378
|
} else if (ws->parser.head.op_code == 8) {
|
373
|
-
/* close */
|
379
|
+
/* op-code == close */
|
374
380
|
websocket_close(ws);
|
375
381
|
if (ws->parser.head2.op_code == ws->parser.head.op_code)
|
376
382
|
goto reset_parser;
|
@@ -378,8 +384,7 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
378
384
|
/* ping */
|
379
385
|
// write Pong - including ping data...
|
380
386
|
websocket_write_impl(sockfd, read_buffer.buffer + read_buffer.pos,
|
381
|
-
|
382
|
-
ws->parser.state.client);
|
387
|
+
data_len, 10, 1, 1, ws->parser.client);
|
383
388
|
if (ws->parser.head2.op_code == ws->parser.head.op_code)
|
384
389
|
goto reset_parser;
|
385
390
|
} else if (ws->parser.head.op_code == 10) {
|
@@ -393,26 +398,34 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
393
398
|
goto reset_parser;
|
394
399
|
} else {
|
395
400
|
/* WTF? */
|
396
|
-
|
397
|
-
|
401
|
+
// fprintf(stderr, "%p websocket reached a WTF?! state..."
|
402
|
+
// "op1: %i , op2: %i\n",
|
403
|
+
// (void *)ws, ws->parser.head.op_code,
|
404
|
+
// ws->parser.head2.op_code);
|
405
|
+
fprintf(stderr, "ERROR Websockets: protocol error, disconnecting.\n");
|
406
|
+
sock_close(sockfd);
|
407
|
+
return;
|
398
408
|
}
|
399
|
-
// not done, but move the pos marker along
|
400
|
-
read_buffer.pos += ws->parser.data_len;
|
401
|
-
continue;
|
402
409
|
|
403
410
|
reset_parser:
|
411
|
+
ws->length = 0;
|
412
|
+
// clear the parser's multi-frame state
|
413
|
+
*((char *)(&(ws->parser.head2))) = 0;
|
414
|
+
ws->parser.sdata.masked = 0;
|
415
|
+
reset_state:
|
404
416
|
// move the pos marker along - in case we have more then one frame in the
|
405
417
|
// buffer
|
406
|
-
read_buffer.pos +=
|
407
|
-
//
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
418
|
+
read_buffer.pos += data_len;
|
419
|
+
// reset parser state
|
420
|
+
ws->parser.state.has_len = 0;
|
421
|
+
ws->parser.state.at_len = 0;
|
422
|
+
ws->parser.state.has_mask = 0;
|
423
|
+
ws->parser.state.at_mask = 0;
|
424
|
+
ws->parser.state.has_head = 0;
|
425
|
+
ws->parser.sdata.size = 0;
|
426
|
+
*((char *)(&(ws->parser.head))) = 0;
|
427
|
+
ws->parser.received = ws->parser.length = ws->parser.psize.len2 =
|
428
|
+
data_len = 0;
|
416
429
|
}
|
417
430
|
}
|
418
431
|
#undef ws
|
@@ -733,8 +746,7 @@ void *websocket_set_udata(ws_s *ws, void *udata) {
|
|
733
746
|
/** Writes data to the websocket. Returns -1 on failure (0 on success). */
|
734
747
|
int websocket_write(ws_s *ws, void *data, size_t size, uint8_t is_text) {
|
735
748
|
if (sock_isvalid(ws->fd)) {
|
736
|
-
websocket_write_impl(ws->fd, data, size, is_text, 1, 1,
|
737
|
-
ws->parser.state.client);
|
749
|
+
websocket_write_impl(ws->fd, data, size, is_text, 1, 1, ws->parser.client);
|
738
750
|
return 0;
|
739
751
|
}
|
740
752
|
return -1;
|
@@ -809,6 +821,7 @@ struct websocket_multi_write {
|
|
809
821
|
spn_lock_i lock;
|
810
822
|
size_t count;
|
811
823
|
size_t length;
|
824
|
+
uint8_t as_client;
|
812
825
|
uint8_t buffer[];
|
813
826
|
};
|
814
827
|
|
@@ -832,7 +845,8 @@ static void ws_finish_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
|
|
832
845
|
|
833
846
|
static void ws_direct_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
|
834
847
|
struct websocket_multi_write *multi = arg;
|
835
|
-
(
|
848
|
+
if (((ws_s *)(_ws))->parser.client != multi->as_client)
|
849
|
+
return;
|
836
850
|
|
837
851
|
sock_packet_s *packet = sock_checkout_packet();
|
838
852
|
*packet = (sock_packet_s){
|
@@ -852,6 +866,8 @@ static void ws_direct_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
|
|
852
866
|
|
853
867
|
static void ws_check_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
|
854
868
|
struct websocket_multi_write *multi = arg;
|
869
|
+
if (((ws_s *)(_ws))->parser.client != multi->as_client)
|
870
|
+
return;
|
855
871
|
if (multi->if_callback((void *)_ws, multi->arg))
|
856
872
|
ws_direct_multi_write(fd, _ws, arg);
|
857
873
|
}
|
@@ -868,6 +884,7 @@ void websocket_write_each(ws_s *ws_originator, void *data, size_t len,
|
|
868
884
|
multi->arg = arg;
|
869
885
|
multi->lock = SPN_LOCK_INIT;
|
870
886
|
multi->count = 1;
|
887
|
+
multi->as_client = as_client;
|
871
888
|
server_each((ws_originator ? ws_originator->fd : -1), WEBSOCKET_ID_STR,
|
872
889
|
(if_callback ? ws_check_multi_write : ws_direct_multi_write),
|
873
890
|
multi, ws_finish_multi_write);
|
data/ext/iodine/websockets.h
CHANGED
@@ -136,9 +136,11 @@ If an `if_callback` is provided, the data will be written to the connection only
|
|
136
136
|
if the `if_callback` returns TRUE (a non zero value).
|
137
137
|
|
138
138
|
The `as_client` is a boolean value indicating if the data should be masked (sent
|
139
|
-
to a server, in client mode) or not.
|
140
|
-
|
141
|
-
|
139
|
+
to a server, in client mode) or not. The data will only be sent to the
|
140
|
+
connections matching the required state (i.e., if `as_client == 1`, the data
|
141
|
+
will only be sent to connections where this process behaves as a websocket
|
142
|
+
client). If some data should be sent in client mode and other in server mode,
|
143
|
+
than the function must be called twice.
|
142
144
|
*/
|
143
145
|
void websocket_write_each(ws_s *ws_originator, void *data, size_t len,
|
144
146
|
uint8_t is_text, uint8_t as_client,
|
data/iodine.gemspec
CHANGED
@@ -36,8 +36,8 @@ Gem::Specification.new do |spec|
|
|
36
36
|
|
37
37
|
spec.requirements << 'A Unix based system: Linux / macOS / BSD.'
|
38
38
|
spec.requirements << 'An updated C compiler.'
|
39
|
-
spec.requirements << 'Ruby >= 2.2.2'
|
40
|
-
spec.requirements << 'Ruby >= 2.3.0
|
39
|
+
spec.requirements << 'Ruby >= 2.2.2 required for Rack.'
|
40
|
+
spec.requirements << 'Ruby >= 2.3.0 recommended.'
|
41
41
|
|
42
42
|
spec.add_development_dependency 'bundler', '~> 1.10'
|
43
43
|
spec.add_development_dependency 'rake', '~> 12.0'
|
data/lib/iodine/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iodine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-02-
|
11
|
+
date: 2017-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -199,8 +199,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
199
|
requirements:
|
200
200
|
- 'A Unix based system: Linux / macOS / BSD.'
|
201
201
|
- An updated C compiler.
|
202
|
-
- Ruby >= 2.2.2
|
203
|
-
- Ruby >= 2.3.0
|
202
|
+
- Ruby >= 2.2.2 required for Rack.
|
203
|
+
- Ruby >= 2.3.0 recommended.
|
204
204
|
rubyforge_project:
|
205
205
|
rubygems_version: 2.6.8
|
206
206
|
signing_key:
|