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 +4 -4
- data/CHANGELOG.md +15 -1
- data/SPEC-Websocket-Draft.md +7 -0
- data/bin/ws-shootout +100 -0
- data/ext/iodine/iodine_core.h +4 -4
- data/ext/iodine/iodine_websocket.c +47 -1
- data/ext/iodine/mempool.h +9 -5
- data/ext/iodine/rb-libasync.h +4 -4
- data/ext/iodine/websockets.c +149 -24
- data/ext/iodine/websockets.h +15 -0
- data/iodine.gemspec +1 -1
- data/lib/iodine/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b66fc52e683d2179b50db49e0c3912b11465cfc
|
4
|
+
data.tar.gz: c52e3bcb4f88dc8134a6fd0cd8c933569a7feccf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce2c2217e792f1cd6b4178e0a12cfc80bf5eb7fef89113ce232799be5fb2797da2c09e399c324c189019434ee566d4083ff68cdf288eacc846c4042e76709cc7
|
7
|
+
data.tar.gz: c7aae4d6787b9d1442a978f66d83610f2391819e1c643099ed2fad2c78ae36d484d66e122295044b90d6d8039ab9a7fb013ef7ef9e95fa037fad3843213c3565
|
data/CHANGELOG.md
CHANGED
@@ -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.
|
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
|
|
data/SPEC-Websocket-Draft.md
CHANGED
@@ -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.
|
data/bin/ws-shootout
ADDED
@@ -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
|
+
# };
|
data/ext/iodine/iodine_core.h
CHANGED
@@ -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
|
28
|
-
#define
|
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
|
-
|
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
|
-
|
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);
|
data/ext/iodine/mempool.h
CHANGED
@@ -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 & (~
|
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
|
-
|
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);
|
data/ext/iodine/rb-libasync.h
CHANGED
@@ -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
|
28
|
-
#define
|
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
|
-
|
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
|
-
|
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)
|
data/ext/iodine/websockets.c
CHANGED
@@ -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
|
-
|
453
|
-
|
454
|
-
|
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
|
-
|
471
|
-
|
472
|
-
|
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 =
|
530
|
+
.masked = client,
|
487
531
|
.length = htons(len)};
|
488
|
-
|
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 +
|
491
|
-
|
492
|
-
|
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
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
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
|
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 =
|
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
|
+
}
|
data/ext/iodine/websockets.h
CHANGED
@@ -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);
|
data/iodine.gemspec
CHANGED
@@ -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', '~>
|
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" \
|
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.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-
|
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: '
|
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: '
|
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.
|
206
|
+
rubygems_version: 2.6.8
|
206
207
|
signing_key:
|
207
208
|
specification_version: 4
|
208
209
|
summary: Iodine - leveraging C for Ruby servers.
|