iodine 0.2.10 → 0.2.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30444cc800decb8b3339bdbc34e499e8fa8d7d75
4
- data.tar.gz: 6657c29ca28a5aa8ef3dd3f50e848020f256b674
3
+ metadata.gz: 1b66fc52e683d2179b50db49e0c3912b11465cfc
4
+ data.tar.gz: c52e3bcb4f88dc8134a6fd0cd8c933569a7feccf
5
5
  SHA512:
6
- metadata.gz: be709de0f327815d3c8366f388395f8dc26c4a1789a25d5c80e04445efd9508c746a95df6538d34fcee405c0f7458d6fa9575b256483124e72db042e5c8ad907
7
- data.tar.gz: f7d0fb12ed10468ffc2a1b3297d2a214da07753570873dc79224a1ae8279034a1abe72f2a78801850606921ae851874b91854380a4b0003d914c199dfbe08589
6
+ metadata.gz: ce2c2217e792f1cd6b4178e0a12cfc80bf5eb7fef89113ce232799be5fb2797da2c09e399c324c189019434ee566d4083ff68cdf288eacc846c4042e76709cc7
7
+ data.tar.gz: c7aae4d6787b9d1442a978f66d83610f2391819e1c643099ed2fad2c78ae36d484d66e122295044b90d6d8039ab9a7fb013ef7ef9e95fa037fad3843213c3565
@@ -8,7 +8,21 @@ 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.10 (next release)
11
+ Change log v.0.2.12 (next release)
12
+
13
+ ***
14
+
15
+ Change log v.0.2.11
16
+
17
+ **Fix**: C layer memory pool had a race-condition that could have caused, in some unlikely events, memory allocation failure for Websocket protocol handlers. This had now been addressed and fixed.
18
+
19
+ **Experimental feature**: added an `each_write` feature to allow direct `write` operations that write data to all open Websocket connections sharing the same process (worker). When this method is called without the optional block, the data will be sent without the need to acquire the Ruby GIL.
20
+
21
+ **Update**: lessons learned from `facil.io` have been implemented for better compatibility of Iodine's core C layer.
22
+
23
+ ***
24
+
25
+ Change log v.0.2.10
12
26
 
13
27
  **Update**: added documentation and an extra helper method to set a connection's timeout when using custom protocols (Iodine as an EventMachine alternative).
14
28
 
@@ -1,3 +1,10 @@
1
+ ### Draft Inactivity Notice
2
+
3
+ This proposed draft is only implemented by Iodine and hadn't seen external activity in the last four months. It is unlikely that this initiative will grow to become a community convention.
4
+
5
+ I still believe it's important to separate the Websocket server from the Websocket application (much like Rack did for HTTP).n I hope that in the future a community convention for this separation of concerns can be achieved.
6
+
7
+ ---
1
8
  ## Rack Websockets
2
9
 
3
10
  This is the proposed Websocket support extension for Rack servers.
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Dir.chdir(File.expand_path(File.join('..', '..'), __FILE__))
4
+ puts `rake clean`
5
+ puts `rake compile`
6
+
7
+ require 'benchmark'
8
+ require 'json'
9
+ $LOAD_PATH.unshift File.expand_path(File.join('..', '..', 'lib'), __FILE__)
10
+ require 'bundler/setup'
11
+ require 'iodine'
12
+ require 'rack'
13
+
14
+
15
+ class ShootoutApp
16
+ # the default HTTP response
17
+ def self.call(env)
18
+ if env['upgrade.websocket?'.freeze] # && env['HTTP_UPGRADE'.freeze] =~ /websocket/i
19
+ env['upgrade.websocket'.freeze] = ShootoutApp.new
20
+ return [0, {}, []]
21
+ end
22
+ out = "ENV:\r\n#{env.to_a.map { |h| "#{h[0]}: #{h[1]}" } .join "\n"}\n"
23
+ request = Rack::Request.new(env)
24
+ out += "\nRequest Path: #{request.path_info}\nParams:\r\n#{request.params.to_a.map { |h| "#{h[0]}: #{h[1]}" } .join "\n"}\n" unless request.params.empty?
25
+ [200, { 'Content-Length' => out.length, 'Content-Type' => 'text/plain; charset=UTF-8;' }, [out]]
26
+ end
27
+ # we won't be using AutoDispatch, but directly using the `on_message` callback.
28
+ def on_message data
29
+ cmd, payload = JSON(data).values_at('type', 'payload')
30
+ if cmd == 'echo'
31
+ write({type: 'echo', payload: payload}.to_json)
32
+ else
33
+ msg = {type: 'broadcast', payload: payload}.to_json
34
+ # Iodine::Websocket.each {|ws| ws.write msg}
35
+ Iodine::Websocket.multiwrite(msg) # {|ws| true }
36
+ write({type: "broadcastResult", payload: payload}.to_json)
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ # create the server object and setup any settings we might need.
43
+ Iodine.threads ||= 4
44
+ Iodine.processes ||= 1
45
+ Iodine::Rack.public = nil
46
+ Iodine::Rack.app = ShootoutApp
47
+ Iodine.start
48
+
49
+ # server.on_http= Proc.new do |env|
50
+ # # [200, {"Content-Length".freeze => "12".freeze}, ["Hello World!".freeze]];
51
+ # if env["HTTP_UPGRADE".freeze] =~ /websocket/i.freeze
52
+ # env['iodine.websocket'.freeze] = WSEcho.new
53
+ # [0,{}, []]
54
+ # else
55
+ # req = Rack::Request.new env
56
+ # res = Rack::Response.new
57
+ # res.write "Hello World!".freeze
58
+ # res.to_a
59
+ # end
60
+ # end
61
+
62
+ # server.on_start do
63
+ # server.run_every(1000) {puts "#{server.connection_count} clients connected."}
64
+ # end
65
+
66
+ # puts "Press enter to start (#{Process.pid})"
67
+ # gets
68
+
69
+ # def nag
70
+ # puts `ab -n 200000 -c 2000 -k http://127.0.0.1:3000/`
71
+ # sleep 2
72
+ # end
73
+ #
74
+ # nag while true
75
+ #
76
+ # def nag
77
+ # puts `wrk -c2000 -d10 -t4 http://localhost:3000/`
78
+ # sleep 3
79
+ # end
80
+ #
81
+ # nag while true
82
+
83
+ # ab -n 100000 -c 200 -k http://127.0.0.1:3000/
84
+ # ab -n 100000 -c 4000 -k http://127.0.0.1:3000/
85
+ # ab -n 1000000 -c 20000 -k http://127.0.0.1:3000/
86
+ # ~/ruby/wrk/wrk -c400 -d10 -t12 http://localhost:3000/
87
+ # wrk -c200 -d4 -t12 http://localhost:3000/
88
+ # ab -n 2000 -c 20 -H "Connection: close" http://127.0.0.1:3000/
89
+ # RACK_ENV="production" rackup -p 3000 -s iodine
90
+
91
+ # thor --amount 5000 ws://localhost:3000/echo
92
+ # thor --amount 5000 ws://localhost:3000/broadcast
93
+
94
+ # ws = new WebSocket("ws://localhost:3000"); ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data);}; ws.onclose = function(e) {console.log("closed")}; ws.onopen = function(e) {ws.send("hi");};
95
+ # for(i = 0; i< 256; i++) {
96
+ # ws = new WebSocket("ws://localhost:3000");
97
+ # ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data); e.target.close(); };
98
+ # ws.onclose = function(e) {console.log("closed")};
99
+ # ws.onopen = function(e) {e.target.send("hi");};
100
+ # };
@@ -24,8 +24,8 @@ Feel free to copy, use and enjoy according to the license provided.
24
24
  #include <stdlib.h>
25
25
  #include <string.h>
26
26
 
27
- #ifndef __unused
28
- #define __unused __attribute__((unused))
27
+ #ifndef UNUSED_FUNC
28
+ #define UNUSED_FUNC __attribute__((unused))
29
29
  #endif
30
30
 
31
31
  extern rb_encoding *BinaryEncoding;
@@ -52,10 +52,10 @@ extern ID fd_var_id;
52
52
  extern ID timeout_var_id;
53
53
  extern ID to_s_method_id;
54
54
 
55
- __unused static inline void iodine_set_fd(VALUE handler, intptr_t fd) {
55
+ UNUSED_FUNC static inline void iodine_set_fd(VALUE handler, intptr_t fd) {
56
56
  rb_ivar_set(handler, fd_var_id, LONG2NUM((long)fd));
57
57
  }
58
- __unused static inline intptr_t iodine_get_fd(VALUE handler) {
58
+ UNUSED_FUNC static inline intptr_t iodine_get_fd(VALUE handler) {
59
59
  return ((intptr_t)NUM2LONG(rb_ivar_get(handler, fd_var_id)));
60
60
  }
61
61
  /* *****************************************************************************
@@ -193,10 +193,11 @@ static void iodine_perform_defer(intptr_t uuid, protocol_s *protocol,
193
193
  RubyCaller.call2((VALUE)arg, call_proc_id, 1, &obj);
194
194
  Registry.remove((VALUE)arg);
195
195
  }
196
+
196
197
  static void iodine_defer_fallback(intptr_t uuid, void *arg) {
197
198
  (void)(uuid);
198
199
  Registry.remove((VALUE)arg);
199
- };
200
+ }
200
201
 
201
202
  /**
202
203
  Schedules a block of code to execute at a later time, **if** the connection is
@@ -239,6 +240,49 @@ static VALUE iodine_defer(int argc, VALUE *argv, VALUE self) {
239
240
  return block;
240
241
  }
241
242
 
243
+ /* *****************************************************************************
244
+ Websocket Multi-Write
245
+ */
246
+
247
+ static uint8_t iodine_ws_if_callback(ws_s *ws, void *block) {
248
+ VALUE handler = get_handler(ws);
249
+ uint8_t ret = 0;
250
+ if (handler)
251
+ ret = RubyCaller.call2((VALUE)block, call_proc_id, 1, &handler);
252
+ return ret && ret != Qnil && ret != Qfalse;
253
+ }
254
+
255
+ /**
256
+ * Writes data to all the Websocket connections sharing the same process
257
+ * (worker) except `self`.
258
+ *
259
+ * If a block is given, it will be passed each Websocket connection in turn
260
+ * (much like `each`) and send the data only if the block returns a "truthy"
261
+ * value (i.e. NOT `false` or `nil`).
262
+ *
263
+ * See both {#write} and {#each} for more details.
264
+ */
265
+ static VALUE iodine_ws_multiwrite(VALUE self, VALUE data) {
266
+ Check_Type(data, T_STRING);
267
+ ws_s *ws = get_ws(self);
268
+ // if ((void *)ws == (void *)0x04 || (void *)data == (void *)0x04 ||
269
+ // RSTRING_PTR(data) == (void *)0x04)
270
+ // fprintf(stderr, "iodine_ws_write: self = %p ; data = %p\n"
271
+ // "\t\tString ptr: %p, String length: %lu\n",
272
+ // (void *)ws, (void *)data, RSTRING_PTR(data), RSTRING_LEN(data));
273
+ if (!ws || ((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
274
+ ws = NULL;
275
+
276
+ VALUE block = Qnil;
277
+ if (rb_block_given_p())
278
+ block = rb_block_proc();
279
+ websocket_write_each(ws, RSTRING_PTR(data), RSTRING_LEN(data),
280
+ rb_enc_get(data) == UTF8Encoding, 0,
281
+ ((block == Qnil) ? NULL : iodine_ws_if_callback),
282
+ (void *)block);
283
+ return Qtrue;
284
+ }
285
+
242
286
  /* *****************************************************************************
243
287
  Websocket task performance
244
288
  */
@@ -472,6 +516,7 @@ void Init_iodine_websocket(void) {
472
516
  rb_define_method(rWebsocket, "on_close", empty_func, 0);
473
517
  rb_define_method(rWebsocket, "on_ready", empty_func, 0);
474
518
  rb_define_method(rWebsocket, "write", iodine_ws_write, 1);
519
+ rb_define_method(rWebsocket, "each_write", iodine_ws_multiwrite, 1);
475
520
  rb_define_method(rWebsocket, "close", iodine_ws_close, 0);
476
521
 
477
522
  rb_define_method(rWebsocket, "uuid", iodine_ws_uuid, 0);
@@ -483,6 +528,7 @@ void Init_iodine_websocket(void) {
483
528
 
484
529
  rb_define_singleton_method(rWebsocket, "each", iodine_ws_class_each, 0);
485
530
  rb_define_singleton_method(rWebsocket, "defer", iodine_class_defer, 1);
531
+ rb_define_singleton_method(rWebsocket, "each_write", iodine_ws_multiwrite, 1);
486
532
 
487
533
  rWebsocketClass = rb_define_module_under(IodineBase, "WebsocketClass");
488
534
  rb_define_method(rWebsocketClass, "each", iodine_ws_class_each, 0);
@@ -204,7 +204,7 @@ static UNUSED_FUNC void *mempool_malloc(size_t size) {
204
204
  if (!size)
205
205
  return NULL;
206
206
  if (size & 15) {
207
- size = (size & (~16)) + 16;
207
+ size = (size & (~15)) + 16;
208
208
  }
209
209
 
210
210
  size += sizeof(struct mempool_reserved_slice_s_offset);
@@ -213,8 +213,9 @@ static UNUSED_FUNC void *mempool_malloc(size_t size) {
213
213
 
214
214
  if (size > (MEMPOOL_BLOCK_SIZE - (sizeof(mempool_reserved_slice_s) << 1)))
215
215
  goto alloc_indi;
216
- slice = mempool_reserved_pool.available;
216
+
217
217
  MEMPOOL_LOCK();
218
+ slice = mempool_reserved_pool.available;
218
219
  while (slice && slice->offset.ahead < size)
219
220
  slice = slice->next;
220
221
  if (slice) {
@@ -249,6 +250,9 @@ static UNUSED_FUNC void *mempool_malloc(size_t size) {
249
250
  }
250
251
 
251
252
  if (slice->offset.ahead > (size + sizeof(mempool_reserved_slice_s))) {
253
+ if (slice->offset.ahead & MEMPOOL_USED_MARKER) {
254
+ fprintf(stderr, "mempool ERROR: allocating an allocated slice!\n");
255
+ }
252
256
  /* cut the slice in two */
253
257
  mempool_reserved_slice_s *tmp =
254
258
  (mempool_reserved_slice_s *)(((uintptr_t)slice) + size);
@@ -432,9 +436,9 @@ static UNUSED_FUNC void *mempool_realloc(void *ptr, size_t size) {
432
436
  if ((slice->offset.ahead & MEMPOOL_USED_MARKER) != MEMPOOL_USED_MARKER)
433
437
  goto error;
434
438
 
435
- slice->offset.ahead &= MEMPOOL_SIZE_MASK;
436
-
437
439
  MEMPOOL_LOCK();
440
+
441
+ slice->offset.ahead &= MEMPOOL_SIZE_MASK;
438
442
  /* merge slice with upper boundry */
439
443
  while ((tmp = (mempool_reserved_slice_s *)(((uintptr_t)slice) +
440
444
  slice->offset.ahead))
@@ -704,7 +708,7 @@ static void mempool_speedtest(size_t memtest_repeats, void *(*mlk)(size_t),
704
708
  fr_time = end - start;
705
709
  fprintf(stderr, "* Freeing %lu consecutive blocks %d each: %lu CPU cycles.\n",
706
710
  memtest_repeats, MEMTEST_SLICE, fr_time);
707
- fprintf(stderr, "* Freeing pointer array %p.\n", pntrs);
711
+ fprintf(stderr, "* Freeing pointer array %p.\n", (void *)pntrs);
708
712
  fr(pntrs);
709
713
 
710
714
  clock_gettime(CLOCK_MONOTONIC, &end_test);
@@ -24,8 +24,8 @@ Portability - used to help port this to different frameworks (i.e. Ruby).
24
24
  #endif
25
25
 
26
26
  /* The unused directive */
27
- #ifndef __unused
28
- #define __unused __attribute__((unused))
27
+ #ifndef UNUSED_FUNC
28
+ #define UNUSED_FUNC __attribute__((unused))
29
29
  #endif
30
30
 
31
31
  /* used here but declared elsewhere */
@@ -40,7 +40,7 @@ static void *_inner_join_with_rbthread(void *rbt) {
40
40
  }
41
41
 
42
42
  /* join a ruby thread */
43
- __unused static void *join_thread(THREAD_TYPE thr) {
43
+ UNUSED_FUNC static void *join_thread(THREAD_TYPE thr) {
44
44
  void *ret = rb_thread_call_with_gvl(_inner_join_with_rbthread, (void *)thr);
45
45
  Registry.remove(thr);
46
46
  return ret;
@@ -68,7 +68,7 @@ static void *create_ruby_thread_gvl(void *_args) {
68
68
  }
69
69
 
70
70
  /* create a ruby thread */
71
- __unused static int create_thread(THREAD_TYPE *thr,
71
+ UNUSED_FUNC static int create_thread(THREAD_TYPE *thr,
72
72
  void *(*thread_func)(void *), void *arg) {
73
73
  struct CreateThreadArgs *data = malloc(sizeof(*data));
74
74
  if (!data)
@@ -192,6 +192,8 @@ static void on_shutdown(intptr_t fd, protocol_s *ws) {
192
192
  /* later */
193
193
  static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
194
194
  char first, char last, char client);
195
+ static size_t websocket_encode(void *buff, void *data, size_t len, char text,
196
+ char first, char last, char client);
195
197
 
196
198
  /* read data from the socket, parse it and invoke the websocket events. */
197
199
  static void on_data(intptr_t sockfd, protocol_s *_ws) {
@@ -333,7 +335,7 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
333
335
  }
334
336
  }
335
337
  // copy here
336
- memcpy(ws->buffer.data + ws->length,
338
+ memcpy((uint8_t *)ws->buffer.data + ws->length,
337
339
  read_buffer.buffer + read_buffer.pos, ws->parser.data_len);
338
340
  ws->length += ws->parser.data_len;
339
341
  }
@@ -446,12 +448,52 @@ static void destroy_ws(ws_s *ws) {
446
448
  /*******************************************************************************
447
449
  Writing to the Websocket
448
450
  */
449
-
450
451
  #define WS_MAX_FRAME_SIZE 65532 // should be less then `unsigned short`
451
452
 
452
- static void websocket_write_impl(intptr_t fd, void *data, size_t len,
453
- char text, /* TODO: add client masking */
454
- char first, char last, char client) {
453
+ // clang-format off
454
+ #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
455
+ # if defined(__has_include)
456
+ # if __has_include(<endian.h>)
457
+ # include <endian.h>
458
+ # elif __has_include(<sys/endian.h>)
459
+ # include <sys/endian.h>
460
+ # endif
461
+ # endif
462
+ # if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
463
+ __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
464
+ # define __BIG_ENDIAN__
465
+ # endif
466
+ #endif
467
+ // clang-format on
468
+
469
+ #ifdef __BIG_ENDIAN__
470
+ /** byte swap 64 bit integer */
471
+ #define bswap64(i) (i)
472
+
473
+ #else
474
+ // TODO: check for __builtin_bswap64
475
+ /** byte swap 64 bit integer */
476
+ #define bswap64(i) \
477
+ ((((i)&0xFFULL) << 56) | (((i)&0xFF00ULL) << 40) | \
478
+ (((i)&0xFF0000ULL) << 24) | (((i)&0xFF000000ULL) << 8) | \
479
+ (((i)&0xFF00000000ULL) >> 8) | (((i)&0xFF0000000000ULL) >> 24) | \
480
+ (((i)&0xFF000000000000ULL) >> 40) | (((i)&0xFF00000000000000ULL) >> 56))
481
+
482
+ #endif
483
+
484
+ static void websocket_mask(void *dest, void *data, size_t len) {
485
+ /* a semi-random 4 byte mask */
486
+ uint32_t mask = ((rand() << 7) ^ ((uintptr_t)dest >> 13));
487
+ /* place mask at head of data */
488
+ dest = (uint8_t *)dest + 4;
489
+ memcpy(dest, &mask, 4);
490
+ /* TODO: optimize this */
491
+ for (size_t i = 0; i < len; i++) {
492
+ ((uint8_t *)dest)[i] = ((uint8_t *)data)[i] ^ ((uint8_t *)(&mask))[i & 3];
493
+ }
494
+ }
495
+ static size_t websocket_encode(void *buff, void *data, size_t len, char text,
496
+ char first, char last, char client) {
455
497
  if (len < 126) {
456
498
  struct {
457
499
  unsigned op_code : 4;
@@ -465,11 +507,13 @@ static void websocket_write_impl(intptr_t fd, void *data, size_t len,
465
507
  .fin = last,
466
508
  .size = len,
467
509
  .masked = client};
468
- char buff[len + (client ? 6 : 2)];
469
510
  memcpy(buff, &head, 2);
470
- memcpy(buff + (client ? 6 : 2), data, len);
471
- sock_write(fd, buff, len + 2);
472
- } else if (len <= WS_MAX_FRAME_SIZE) {
511
+ if (client)
512
+ websocket_mask((uint8_t *)buff + 2, data, len);
513
+ else
514
+ memcpy((uint8_t *)buff + 2, data, len);
515
+ return len + 2 + (client ? 4 : 0);
516
+ } else if (len < (1UL << 16)) {
473
517
  /* head is 4 bytes */
474
518
  struct {
475
519
  unsigned op_code : 4;
@@ -483,26 +527,64 @@ static void websocket_write_impl(intptr_t fd, void *data, size_t len,
483
527
  } head = {.op_code = (first ? (text ? 1 : 2) : 0),
484
528
  .fin = last,
485
529
  .size = 126,
486
- .masked = 0,
530
+ .masked = client,
487
531
  .length = htons(len)};
488
- if (len >> 15) { // if len is larger then 32,767 Bytes.
532
+ memcpy(buff, &head, 4);
533
+ if (client)
534
+ websocket_mask((uint8_t *)buff + 4, data, len);
535
+ else
536
+ memcpy((uint8_t *)buff + 4, data, len);
537
+ return len + 4 + (client ? 4 : 0);
538
+ }
539
+ /* Really Long Message */
540
+ struct {
541
+ unsigned op_code : 4;
542
+ unsigned rsv3 : 1;
543
+ unsigned rsv2 : 1;
544
+ unsigned rsv1 : 1;
545
+ unsigned fin : 1;
546
+ unsigned size : 7;
547
+ unsigned masked : 1;
548
+ } head = {
549
+ .op_code = (first ? (text ? 1 : 2) : 0),
550
+ .fin = last,
551
+ .size = 127,
552
+ .masked = client,
553
+ };
554
+ memcpy(buff, &head, 2);
555
+ ((size_t *)((uint8_t *)buff + 2))[0] = bswap64(len);
556
+ if (client)
557
+ websocket_mask((uint8_t *)buff + 10, data, len);
558
+ else
559
+ memcpy((uint8_t *)buff + 10, data, len);
560
+ return len + 10 + (client ? 4 : 0);
561
+ }
562
+
563
+ static void websocket_write_impl(intptr_t fd, void *data, size_t len,
564
+ char text, /* TODO: add client masking */
565
+ char first, char last, char client) {
566
+ if (len < 126) {
567
+ char buff[len + (client ? 6 : 2)];
568
+ len = websocket_encode(buff, data, len, text, first, last, client);
569
+ sock_write(fd, buff, len);
570
+ } else if (len <= WS_MAX_FRAME_SIZE) {
571
+ if (len >= BUFFER_PACKET_SIZE) { // if len is larger then a single packet.
489
572
  /* head MUST be 4 bytes */
490
- void *buff = malloc(len + (client ? 8 : 4));
491
- memcpy(buff, &head, 4);
492
- memcpy(buff + (client ? 8 : 4), data, len);
493
- sock_write2(.fduuid = fd, .buffer = buff, .length = len + 4, .move = 1);
573
+ void *buff = malloc(len + 4);
574
+ len = websocket_encode(buff, data, len, text, first, last, client);
575
+ sock_write2(.fduuid = fd, .buffer = buff, .length = len, .move = 1);
494
576
  } else {
495
- /* head MUST be 4 bytes */
496
- char buff[len + (client ? 8 : 4)];
497
- memcpy(buff, &head, 4);
498
- memcpy(buff + (client ? 8 : 4), data, len);
499
- sock_write(fd, buff, len + 4);
577
+ sock_packet_s *packet = sock_checkout_packet();
578
+ packet->length = websocket_encode(packet->buffer, data, len, text, first,
579
+ last, client);
580
+ packet->metadata.can_interrupt = 1;
581
+ sock_send_packet(fd, packet);
500
582
  }
501
583
  } else {
502
584
  /* frame fragmentation is better for large data then large frames */
503
585
  while (len > WS_MAX_FRAME_SIZE) {
504
586
  websocket_write_impl(fd, data, WS_MAX_FRAME_SIZE, text, first, 0, client);
505
- data += WS_MAX_FRAME_SIZE;
587
+ data = ((uint8_t *)data) + WS_MAX_FRAME_SIZE;
506
588
  first = 0;
507
589
  len -= WS_MAX_FRAME_SIZE;
508
590
  }
@@ -604,7 +686,8 @@ ssize_t websocket_upgrade(websocket_settings_s settings) {
604
686
  recv_str = http_request_find_header(settings.request,
605
687
  "sec-websocket-extensions", 24);
606
688
  // if (recv_str != NULL)
607
- // http_response_write_header(response, .name = "Sec-Websocket-Extensions",
689
+ // http_response_write_header(response, .name =
690
+ // "Sec-Websocket-Extensions",
608
691
  // .name_length = 24);
609
692
 
610
693
  goto cleanup;
@@ -626,7 +709,7 @@ cleanup:
626
709
  server_set_timeout(ws->fd, settings.timeout);
627
710
  // call the on_open callback
628
711
  if (settings.on_open)
629
- server_task(ws->fd, on_open, settings.on_open, NULL);
712
+ server_task(ws->fd, on_open, (void *)settings.on_open, NULL);
630
713
  spn_unlock(&ws->protocol.callback_lock);
631
714
  return 0;
632
715
  }
@@ -688,7 +771,8 @@ struct WSTask {
688
771
  void (*on_finish)(ws_s *, void *);
689
772
  void *arg;
690
773
  };
691
- /** Performs a task on each websocket connection that shares the same process */
774
+ /** Performs a task on each websocket connection that shares the same process
775
+ */
692
776
  static void perform_ws_task(intptr_t fd, protocol_s *_ws, void *_arg) {
693
777
  (void)(fd);
694
778
  struct WSTask *tsk = _arg;
@@ -717,3 +801,44 @@ void websocket_each(ws_s *ws_originator,
717
801
  server_each((ws_originator ? ws_originator->fd : -1), WEBSOCKET_ID_STR,
718
802
  perform_ws_task, tsk, finish_ws_task);
719
803
  }
804
+ /*******************************************************************************
805
+ Multi-Write (direct broadcast) Implementation
806
+ */
807
+ struct websocket_multi_write {
808
+ uint8_t (*if_callback)(ws_s *ws_to, void *arg);
809
+ void *arg;
810
+ size_t length;
811
+ uint8_t buffer[];
812
+ };
813
+
814
+ static void ws_finish_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
815
+ (void)(fd);
816
+ (void)(_ws);
817
+ mempool_free(arg);
818
+ }
819
+ static void ws_check_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
820
+ struct websocket_multi_write *multi = arg;
821
+ if (multi->if_callback((void *)_ws, multi->arg))
822
+ sock_write(fd, multi->buffer, multi->length);
823
+ }
824
+
825
+ static void ws_direct_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
826
+ struct websocket_multi_write *multi = arg;
827
+ (void)(_ws);
828
+ sock_write(fd, multi->buffer, multi->length);
829
+ }
830
+
831
+ void websocket_write_each(ws_s *ws_originator, void *data, size_t len,
832
+ uint8_t is_text, uint8_t as_client,
833
+ uint8_t (*if_callback)(ws_s *ws_to, void *arg),
834
+ void *arg) {
835
+ struct websocket_multi_write *multi =
836
+ mempool_malloc(len + 14 /* max head size */ + sizeof(*multi));
837
+ multi->length =
838
+ websocket_encode(multi->buffer, data, len, is_text, 1, 1, as_client);
839
+ multi->if_callback = if_callback;
840
+ multi->arg = arg;
841
+ server_each((ws_originator ? ws_originator->fd : -1), WEBSOCKET_ID_STR,
842
+ (if_callback ? ws_check_multi_write : ws_direct_multi_write),
843
+ multi, ws_finish_multi_write);
844
+ }
@@ -129,6 +129,21 @@ void websocket_each(ws_s *ws_originator,
129
129
  void (*task)(ws_s *ws_target, void *arg), void *arg,
130
130
  void (*on_finish)(ws_s *ws_originator, void *arg));
131
131
  /**
132
+ Writes data to each websocket connection that shares the same process
133
+ (except the originating `ws_s` connection which is allowed to be NULL).
134
+
135
+ If an `if_callback` is provided, the data will be written to the connection only
136
+ if the `if_callback` returns TRUE (a non zero value).
137
+
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. If some data should be sent in client mode
140
+ and other in server mode, consider using the `if_callback` to make sure the data
141
+ is encoded properly.
142
+ */
143
+ void websocket_write_each(ws_s *ws_originator, void *data, size_t len,
144
+ uint8_t is_text, uint8_t as_client,
145
+ uint8_t (*if_callback)(ws_s *ws_to, void *arg),
146
+ void *arg); /**
132
147
  Counts the number of websocket connections.
133
148
  */
134
149
  size_t websocket_count(ws_s *ws);
@@ -40,7 +40,7 @@ Gem::Specification.new do |spec|
40
40
  spec.requirements << 'Ruby >= 2.3.0 is recommended.'
41
41
 
42
42
  spec.add_development_dependency 'bundler', '~> 1.10'
43
- spec.add_development_dependency 'rake', '~> 10.0'
43
+ spec.add_development_dependency 'rake', '~> 12.0'
44
44
  spec.add_development_dependency 'minitest'
45
45
 
46
46
  # spec.post_install_message = "** WARNING!\n" \
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '0.2.10'.freeze
2
+ VERSION = '0.2.11'.freeze
3
3
  end
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.10
4
+ version: 0.2.11
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-01-24 00:00:00.000000000 Z
11
+ date: 2017-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
61
+ version: '12.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '10.0'
68
+ version: '12.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -114,6 +114,7 @@ files:
114
114
  - bin/test_with_faye
115
115
  - bin/ws-broadcast
116
116
  - bin/ws-echo
117
+ - bin/ws-shootout
117
118
  - exe/iodine
118
119
  - ext/iodine/base64.c
119
120
  - ext/iodine/base64.h
@@ -202,7 +203,7 @@ requirements:
202
203
  - Ruby >= 2.2.2
203
204
  - Ruby >= 2.3.0 is recommended.
204
205
  rubyforge_project:
205
- rubygems_version: 2.5.2
206
+ rubygems_version: 2.6.8
206
207
  signing_key:
207
208
  specification_version: 4
208
209
  summary: Iodine - leveraging C for Ruby servers.