iodine 0.4.3 → 0.4.4
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 +6 -3
- data/SPEC-Websocket-Draft.md +5 -1
- data/ext/iodine/http1.c +1 -8
- data/ext/iodine/iodine_websockets.c +92 -99
- data/ext/iodine/sock.c +3 -2
- data/ext/iodine/websockets.c +3 -10
- data/lib/iodine/version.rb +1 -1
- data/lib/iodine/websocket.rb +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b38604622998dd86c23390d22f206cd77e235437
|
4
|
+
data.tar.gz: d82068cbc80b9ccd7aa0838efbc50e9fdfadc1f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15be0353bd34deb982497787ff2c79bff9a504410a364ba46a4fa23aebf66e1d28441b2cf6103773b02cedcb598a7c2e091d533eaff5b5acc6ce41cdfea7b695
|
7
|
+
data.tar.gz: 96652525a23cbd5fa0e88c69cd490502f857bb609fc3b40db073b1ccac3da3f25879213385a5d732df0b89a09c038c46f531034834cb6b1491c5ae4ac3340e3d
|
data/CHANGELOG.md
CHANGED
@@ -8,12 +8,15 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
8
8
|
|
9
9
|
***
|
10
10
|
|
11
|
-
#### Change log v.0.4.
|
11
|
+
#### Change log v.0.4.4
|
12
|
+
|
13
|
+
**Fix**: fixed an issue related to Ruby 2.3 optimizations of String management (an issue that didn't seem to effect Ruby 2.4). This fix disables the recyclable buffer implemented for the `on_message` Websocket callback. The callback will now receive a copy of the buffer (not the buffer itself), so there is no risk of collisions between the network buffer (managed in C) and the `on_message(data)` String (managed by Ruby).
|
12
14
|
|
13
|
-
|
15
|
+
***
|
14
16
|
|
15
|
-
|
17
|
+
#### Change log v.0.4.3
|
16
18
|
|
19
|
+
**Fix**: fixed a possible issue in fragmented pipelined Websocket messages.
|
17
20
|
|
18
21
|
***
|
19
22
|
|
data/SPEC-Websocket-Draft.md
CHANGED
@@ -23,10 +23,12 @@ The Websocket Callback Object should be a class (or an instance of such class) w
|
|
23
23
|
|
24
24
|
* `on_open()` WILL be called once the upgrade had completed.
|
25
25
|
|
26
|
-
* `on_message(data)` WILL be called when incoming Websocket data is received. `data` will be a String with an encoding of UTF-8 for text messages and `binary` encoding for non-text messages (as specified by the Websocket Protocol).
|
26
|
+
* `on_message(data)` (REQUIRED) WILL be called when incoming Websocket data is received. `data` will be a String with an encoding of UTF-8 for text messages and `binary` encoding for non-text messages (as specified by the Websocket Protocol).
|
27
27
|
|
28
28
|
The *client* **MUST** assume that the `data` String will be a **recyclable buffer** and that it's content will be corrupted the moment the `on_message` callback returns.
|
29
29
|
|
30
|
+
Servers MAY, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional and it is *not* required.
|
31
|
+
|
30
32
|
* `on_ready()` **MAY** be called when the state of the out-going socket buffer changes from full to not full (data can be sent to the socket). **If** `has_pending?` returns `true`, the `on_ready` callback **MUST** be called once the buffer state changes.
|
31
33
|
|
32
34
|
* `on_shutdown()` MAY be called during the server's graceful shutdown process, _before_ the connection is closed and in addition to the `on_close` function (which is called _after_ the connection is closed.
|
@@ -67,6 +69,8 @@ Connection `ping` / `pong`, timeouts and network considerations should be implem
|
|
67
69
|
|
68
70
|
Server settings **MAY** (not required) be provided to allow for customization and adaptation for different network environments or websocket extensions. It is **RECOMMENDED** that any settings be available as command line arguments and **not** incorporated into the application's logic.
|
69
71
|
|
72
|
+
The requirement that the server extends the class of the Websocket Callback Object (instead of the client application doing so explicitly) is designed to allow the client application to be more server agnostic.
|
73
|
+
|
70
74
|
## Upgrading
|
71
75
|
|
72
76
|
* **Server**: When an upgrade request is received, the server will set the `env['upgrade.websocket?']` flag to `true`, indicating that: 1. this specific request is upgradable; and 2. this server supports this specification.
|
data/ext/iodine/http1.c
CHANGED
@@ -111,12 +111,6 @@ static void http1_on_header_found(http_request_s *request,
|
|
111
111
|
((http1_request_s *)request)->header_pos += 1;
|
112
112
|
}
|
113
113
|
|
114
|
-
static void http1_on_data(intptr_t uuid, http1_protocol_s *pr);
|
115
|
-
static void http1_on_data_def(intptr_t uuid, protocol_s *pr, void *ignr) {
|
116
|
-
sock_touch(uuid);
|
117
|
-
http1_on_data(uuid, (http1_protocol_s *)pr);
|
118
|
-
(void)ignr;
|
119
|
-
}
|
120
114
|
/* parse and call callback */
|
121
115
|
static void http1_on_data(intptr_t uuid, http1_protocol_s *pr) {
|
122
116
|
ssize_t result;
|
@@ -230,8 +224,7 @@ static void http1_on_data(intptr_t uuid, http1_protocol_s *pr) {
|
|
230
224
|
/* prevent this connection from hogging the thread by pipelining endless
|
231
225
|
* requests.
|
232
226
|
*/
|
233
|
-
|
234
|
-
.uuid = uuid);
|
227
|
+
facil_force_event(uuid, FIO_EVENT_ON_DATA);
|
235
228
|
return;
|
236
229
|
}
|
237
230
|
}
|
@@ -54,70 +54,70 @@ inline static ws_s *get_ws(VALUE obj) {
|
|
54
54
|
Buffer management - Rubyfy the way the buffer is handled.
|
55
55
|
***************************************************************************** */
|
56
56
|
|
57
|
-
struct buffer_s {
|
58
|
-
|
59
|
-
|
60
|
-
};
|
61
|
-
|
62
|
-
/** returns a buffer_s struct, with a buffer (at least) `size` long. */
|
63
|
-
struct buffer_s create_ws_buffer(ws_s *owner);
|
64
|
-
|
65
|
-
/** returns a buffer_s struct, with a buffer (at least) `size` long. */
|
66
|
-
struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s);
|
67
|
-
|
68
|
-
/** releases an existing buffer. */
|
69
|
-
void free_ws_buffer(ws_s *owner, struct buffer_s);
|
70
|
-
|
71
|
-
/** Sets the initial buffer size. (4Kb)*/
|
72
|
-
#define WS_INITIAL_BUFFER_SIZE 4096
|
73
|
-
|
74
|
-
// buffer increments by 4,096 Bytes (4Kb)
|
75
|
-
#define round_up_buffer_size(size) ((((size) >> 12) + 1) << 12)
|
76
|
-
|
77
|
-
struct buffer_args {
|
78
|
-
|
79
|
-
|
80
|
-
};
|
81
|
-
|
82
|
-
void *ruby_land_buffer(void *_buf) {
|
83
|
-
#define args ((struct buffer_args *)(_buf))
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
#undef args
|
101
|
-
}
|
102
|
-
|
103
|
-
struct buffer_s create_ws_buffer(ws_s *owner) {
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
}
|
108
|
-
|
109
|
-
struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s buffer) {
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
}
|
115
|
-
void free_ws_buffer(ws_s *owner, struct buffer_s buff) {
|
116
|
-
|
117
|
-
|
118
|
-
}
|
119
|
-
|
120
|
-
#undef round_up_buffer_size
|
57
|
+
// struct buffer_s {
|
58
|
+
// void *data;
|
59
|
+
// size_t size;
|
60
|
+
// };
|
61
|
+
//
|
62
|
+
// /** returns a buffer_s struct, with a buffer (at least) `size` long. */
|
63
|
+
// struct buffer_s create_ws_buffer(ws_s *owner);
|
64
|
+
//
|
65
|
+
// /** returns a buffer_s struct, with a buffer (at least) `size` long. */
|
66
|
+
// struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s);
|
67
|
+
//
|
68
|
+
// /** releases an existing buffer. */
|
69
|
+
// void free_ws_buffer(ws_s *owner, struct buffer_s);
|
70
|
+
//
|
71
|
+
// /** Sets the initial buffer size. (4Kb)*/
|
72
|
+
// #define WS_INITIAL_BUFFER_SIZE 4096
|
73
|
+
//
|
74
|
+
// // buffer increments by 4,096 Bytes (4Kb)
|
75
|
+
// #define round_up_buffer_size(size) ((((size) >> 12) + 1) << 12)
|
76
|
+
//
|
77
|
+
// struct buffer_args {
|
78
|
+
// struct buffer_s buffer;
|
79
|
+
// ws_s *ws;
|
80
|
+
// };
|
81
|
+
//
|
82
|
+
// void *ruby_land_buffer(void *_buf) {
|
83
|
+
// #define args ((struct buffer_args *)(_buf))
|
84
|
+
// if (args->buffer.data == NULL) {
|
85
|
+
// VALUE rbbuff = rb_str_buf_new(WS_INITIAL_BUFFER_SIZE);
|
86
|
+
// rb_ivar_set(get_handler(args->ws), iodine_buff_var_id, rbbuff);
|
87
|
+
// rb_str_set_len(rbbuff, 0);
|
88
|
+
// rb_enc_associate(rbbuff, IodineBinaryEncoding);
|
89
|
+
// args->buffer.data = RSTRING_PTR(rbbuff);
|
90
|
+
// args->buffer.size = WS_INITIAL_BUFFER_SIZE;
|
91
|
+
//
|
92
|
+
// } else {
|
93
|
+
// VALUE rbbuff = rb_ivar_get(get_handler(args->ws), iodine_buff_var_id);
|
94
|
+
// rb_str_modify(rbbuff);
|
95
|
+
// rb_str_resize(rbbuff, args->buffer.size);
|
96
|
+
// args->buffer.data = RSTRING_PTR(rbbuff);
|
97
|
+
// args->buffer.size = rb_str_capacity(rbbuff);
|
98
|
+
// }
|
99
|
+
// return NULL;
|
100
|
+
// #undef args
|
101
|
+
// }
|
102
|
+
//
|
103
|
+
// struct buffer_s create_ws_buffer(ws_s *owner) {
|
104
|
+
// struct buffer_args args = {.ws = owner};
|
105
|
+
// RubyCaller.call_c(ruby_land_buffer, &args);
|
106
|
+
// return args.buffer;
|
107
|
+
// }
|
108
|
+
//
|
109
|
+
// struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s buffer) {
|
110
|
+
// buffer.size = round_up_buffer_size(buffer.size);
|
111
|
+
// struct buffer_args args = {.ws = owner, .buffer = buffer};
|
112
|
+
// RubyCaller.call_c(ruby_land_buffer, &args);
|
113
|
+
// return args.buffer;
|
114
|
+
// }
|
115
|
+
// void free_ws_buffer(ws_s *owner, struct buffer_s buff) {
|
116
|
+
// (void)(owner);
|
117
|
+
// (void)(buff);
|
118
|
+
// }
|
119
|
+
//
|
120
|
+
// #undef round_up_buffer_size
|
121
121
|
|
122
122
|
/* *****************************************************************************
|
123
123
|
Websocket Ruby API
|
@@ -641,7 +641,8 @@ void ws_on_open(ws_s *ws) {
|
|
641
641
|
void ws_on_close(ws_s *ws) {
|
642
642
|
VALUE handler = get_handler(ws);
|
643
643
|
if (!handler) {
|
644
|
-
fprintf(stderr,
|
644
|
+
fprintf(stderr,
|
645
|
+
"ERROR: (iodine websockets) Closing a handlerless websocket?!\n");
|
645
646
|
return;
|
646
647
|
}
|
647
648
|
RubyCaller.call(handler, iodine_on_close_func_id);
|
@@ -660,51 +661,43 @@ void ws_on_ready(ws_s *ws) {
|
|
660
661
|
return;
|
661
662
|
RubyCaller.call(handler, iodine_on_ready_func_id);
|
662
663
|
}
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
664
|
+
|
665
|
+
struct ws_on_data_args_s {
|
666
|
+
ws_s *ws;
|
667
|
+
void *data;
|
668
|
+
size_t length;
|
669
|
+
uint8_t is_text;
|
670
|
+
};
|
671
|
+
void *ws_on_data_inGIL(void *args_) {
|
672
|
+
struct ws_on_data_args_s *a = args_;
|
673
|
+
VALUE handler = get_handler(a->ws);
|
674
|
+
if (!handler) {
|
675
|
+
fprintf(stderr, "ERROR: iodine can't find Websocket handler!\n");
|
676
|
+
return NULL;
|
677
|
+
}
|
678
|
+
VALUE buffer = rb_str_new(a->data, a->length);
|
679
|
+
if (a->is_text)
|
670
680
|
rb_enc_associate(buffer, IodineUTF8Encoding);
|
671
681
|
else
|
672
682
|
rb_enc_associate(buffer, IodineBinaryEncoding);
|
673
|
-
|
674
|
-
|
683
|
+
rb_funcallv(handler, iodine_on_message_func_id, 1, &buffer);
|
684
|
+
return NULL;
|
685
|
+
}
|
686
|
+
void ws_on_data(ws_s *ws, char *data, size_t length, uint8_t is_text) {
|
687
|
+
struct ws_on_data_args_s a = {
|
688
|
+
.ws = ws, .data = data, .length = length, .is_text = is_text};
|
689
|
+
RubyCaller.call_c(ws_on_data_inGIL, &a);
|
690
|
+
(void)(data);
|
675
691
|
}
|
676
692
|
|
677
693
|
//////////////
|
678
694
|
// Empty callbacks for default implementations.
|
679
695
|
|
680
|
-
/** Please implement your own callback for this event.
|
681
|
-
*/
|
696
|
+
/** Please implement your own callback for this event. */
|
682
697
|
static VALUE empty_func(VALUE self) {
|
683
698
|
(void)(self);
|
684
699
|
return Qnil;
|
685
700
|
}
|
686
|
-
// /* The `on_message(data)` callback is the main method for any websocket
|
687
|
-
// implementation. It is the only required callback for a websocket handler
|
688
|
-
// (without this handler, errors will occur).
|
689
|
-
//
|
690
|
-
// <b>NOTICE</b>: the data passed to the `on_message` callback is the actual
|
691
|
-
// recycble network buffer, not a copy! <b>Use `data.dup` before moving the data
|
692
|
-
// out of the function's scope</b> to prevent data corruption (i.e. when
|
693
|
-
// using the data within an `each` block). For example (broadcasting):
|
694
|
-
//
|
695
|
-
// def on_message data
|
696
|
-
// msg = data.dup; # data will be overwritten once the function exists.
|
697
|
-
// each {|ws| ws.write msg}
|
698
|
-
// end
|
699
|
-
//
|
700
|
-
// Please override this method and implement your own callback.
|
701
|
-
// */
|
702
|
-
// static VALUE def_dyn_message(VALUE self, VALUE data) {
|
703
|
-
// fprintf(stderr,
|
704
|
-
// "WARNING: websocket handler on_message override missing or "
|
705
|
-
// "bypassed.\n");
|
706
|
-
// return Qnil;
|
707
|
-
// }
|
708
701
|
|
709
702
|
/* *****************************************************************************
|
710
703
|
Upgrading
|
@@ -771,7 +764,7 @@ void Iodine_init_websocket(void) {
|
|
771
764
|
fprintf(stderr, "WTF?!\n"), exit(-1);
|
772
765
|
// // callbacks and handlers
|
773
766
|
rb_define_method(IodineWebsocket, "on_open", empty_func, 0);
|
774
|
-
// rb_define_method(IodineWebsocket, "on_message",
|
767
|
+
// rb_define_method(IodineWebsocket, "on_message", empty_func_message, 1);
|
775
768
|
rb_define_method(IodineWebsocket, "on_shutdown", empty_func, 0);
|
776
769
|
rb_define_method(IodineWebsocket, "on_close", empty_func, 0);
|
777
770
|
rb_define_method(IodineWebsocket, "on_ready", empty_func, 0);
|
data/ext/iodine/sock.c
CHANGED
@@ -862,9 +862,10 @@ ssize_t sock_read(intptr_t uuid, void *buf, size_t count) {
|
|
862
862
|
return rw->read(uuid, buf, count);
|
863
863
|
int old_errno = errno;
|
864
864
|
ssize_t ret = rw->read(uuid, buf, count);
|
865
|
-
|
866
|
-
|
865
|
+
if (ret > 0) {
|
866
|
+
sock_touch(uuid);
|
867
867
|
return ret;
|
868
|
+
}
|
868
869
|
if (ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR ||
|
869
870
|
errno == ENOTCONN)) {
|
870
871
|
errno = old_errno;
|
data/ext/iodine/websockets.c
CHANGED
@@ -115,8 +115,6 @@ struct Websocket {
|
|
115
115
|
struct buffer_s buffer;
|
116
116
|
/** message length (how much of the buffer actually used). */
|
117
117
|
size_t length;
|
118
|
-
/** for fragmenting the `on_data` parsing and message handling. */
|
119
|
-
size_t resume_from;
|
120
118
|
/** parser. */
|
121
119
|
struct {
|
122
120
|
union {
|
@@ -233,12 +231,6 @@ static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
|
|
233
231
|
static size_t websocket_encode(void *buff, void *data, size_t len, char text,
|
234
232
|
char first, char last, char client);
|
235
233
|
|
236
|
-
static void on_data(intptr_t sockfd, protocol_s *_ws);
|
237
|
-
static void on_data_def(intptr_t sockfd, protocol_s *_ws, void *arg) {
|
238
|
-
on_data(sockfd, _ws);
|
239
|
-
(void)arg;
|
240
|
-
}
|
241
|
-
|
242
234
|
/* read data from the socket, parse it and invoke the websocket events. */
|
243
235
|
static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
244
236
|
#define ws ((ws_s *)_ws)
|
@@ -246,10 +238,11 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
246
238
|
return;
|
247
239
|
ssize_t len = 0;
|
248
240
|
ssize_t data_len = 0;
|
241
|
+
read_buffer.pos = 0;
|
242
|
+
|
249
243
|
if ((len = sock_read(sockfd, read_buffer.buffer, WEBSOCKET_READ_MAX)) <= 0)
|
250
244
|
return;
|
251
|
-
|
252
|
-
read_buffer.pos = 0;
|
245
|
+
|
253
246
|
while (read_buffer.pos < len) {
|
254
247
|
// collect the frame's head
|
255
248
|
if (!ws->parser.state.has_head) {
|
data/lib/iodine/version.rb
CHANGED
data/lib/iodine/websocket.rb
CHANGED
@@ -7,6 +7,8 @@ module Iodine
|
|
7
7
|
# * Server <=> Client relations ({Iodine::Websocket#write}, {Iodine::Websocket#close} etc')
|
8
8
|
# * Client <=> Server <=> Client relations ({Iodine::Websocket#subscribe}, {Iodine::Websocket#publish}, etc').
|
9
9
|
# * Task scheduling ({Iodine::Websocket.defer}, {Iodine::Websocket#defer}, {Iodine::Websocket.each}).
|
10
|
+
#
|
11
|
+
# Notice that Websocket callback objects (as specified by the {file:SPEC-Websocket-Draft.md proposed Rack specification} *MUST* provide an `on_message(data)` callback.
|
10
12
|
module Websocket
|
11
13
|
end
|
12
14
|
end
|