rage-iodine 1.7.58
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- data/.github/workflows/ruby.yml +42 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +1098 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/LIMITS.md +41 -0
- data/README.md +782 -0
- data/Rakefile +23 -0
- data/SPEC-PubSub-Draft.md +159 -0
- data/SPEC-WebSocket-Draft.md +239 -0
- data/bin/console +22 -0
- data/bin/info.md +353 -0
- data/bin/mustache_bench.rb +100 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/examples/async_task.ru +92 -0
- data/examples/bates/README.md +3 -0
- data/examples/bates/config.ru +342 -0
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +600 -0
- data/examples/config.ru +59 -0
- data/examples/echo.ru +59 -0
- data/examples/etag.ru +16 -0
- data/examples/hello.ru +29 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/rack3.ru +12 -0
- data/examples/redis.ru +70 -0
- data/examples/shootout.ru +73 -0
- data/examples/sub-protocols.ru +90 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +280 -0
- data/ext/iodine/extconf.rb +110 -0
- data/ext/iodine/fio.c +12096 -0
- data/ext/iodine/fio.h +6390 -0
- data/ext/iodine/fio_cli.c +431 -0
- data/ext/iodine/fio_cli.h +189 -0
- data/ext/iodine/fio_json_parser.h +687 -0
- data/ext/iodine/fio_siphash.c +157 -0
- data/ext/iodine/fio_siphash.h +37 -0
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +649 -0
- data/ext/iodine/fio_tls_openssl.c +1056 -0
- data/ext/iodine/fio_tmpfile.h +50 -0
- data/ext/iodine/fiobj.h +44 -0
- data/ext/iodine/fiobj4fio.h +21 -0
- data/ext/iodine/fiobj_ary.c +333 -0
- data/ext/iodine/fiobj_ary.h +139 -0
- data/ext/iodine/fiobj_data.c +1185 -0
- data/ext/iodine/fiobj_data.h +167 -0
- data/ext/iodine/fiobj_hash.c +409 -0
- data/ext/iodine/fiobj_hash.h +176 -0
- data/ext/iodine/fiobj_json.c +622 -0
- data/ext/iodine/fiobj_json.h +68 -0
- data/ext/iodine/fiobj_mem.h +71 -0
- data/ext/iodine/fiobj_mustache.c +317 -0
- data/ext/iodine/fiobj_mustache.h +62 -0
- data/ext/iodine/fiobj_numbers.c +344 -0
- data/ext/iodine/fiobj_numbers.h +127 -0
- data/ext/iodine/fiobj_str.c +433 -0
- data/ext/iodine/fiobj_str.h +172 -0
- data/ext/iodine/fiobject.c +620 -0
- data/ext/iodine/fiobject.h +654 -0
- data/ext/iodine/hpack.h +1923 -0
- data/ext/iodine/http.c +2736 -0
- data/ext/iodine/http.h +1019 -0
- data/ext/iodine/http1.c +825 -0
- data/ext/iodine/http1.h +29 -0
- data/ext/iodine/http1_parser.h +1835 -0
- data/ext/iodine/http_internal.c +1279 -0
- data/ext/iodine/http_internal.h +248 -0
- data/ext/iodine/http_mime_parser.h +350 -0
- data/ext/iodine/iodine.c +1433 -0
- data/ext/iodine/iodine.h +64 -0
- data/ext/iodine/iodine_caller.c +218 -0
- data/ext/iodine/iodine_caller.h +27 -0
- data/ext/iodine/iodine_connection.c +941 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +420 -0
- data/ext/iodine/iodine_defer.h +6 -0
- data/ext/iodine/iodine_fiobj2rb.h +120 -0
- data/ext/iodine/iodine_helpers.c +282 -0
- data/ext/iodine/iodine_helpers.h +12 -0
- data/ext/iodine/iodine_http.c +1280 -0
- data/ext/iodine/iodine_http.h +23 -0
- data/ext/iodine/iodine_json.c +302 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_mustache.c +567 -0
- data/ext/iodine/iodine_mustache.h +6 -0
- data/ext/iodine/iodine_pubsub.c +580 -0
- data/ext/iodine/iodine_pubsub.h +26 -0
- data/ext/iodine/iodine_rack_io.c +273 -0
- data/ext/iodine/iodine_rack_io.h +20 -0
- data/ext/iodine/iodine_store.c +142 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +346 -0
- data/ext/iodine/iodine_tcp.h +13 -0
- data/ext/iodine/iodine_tls.c +261 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1546 -0
- data/ext/iodine/redis_engine.c +957 -0
- data/ext/iodine/redis_engine.h +79 -0
- data/ext/iodine/resp_parser.h +317 -0
- data/ext/iodine/scheduler.c +173 -0
- data/ext/iodine/scheduler.h +6 -0
- data/ext/iodine/websocket_parser.h +506 -0
- data/ext/iodine/websockets.c +752 -0
- data/ext/iodine/websockets.h +185 -0
- data/iodine.gemspec +50 -0
- data/lib/iodine/connection.rb +61 -0
- data/lib/iodine/json.rb +42 -0
- data/lib/iodine/mustache.rb +113 -0
- data/lib/iodine/pubsub.rb +55 -0
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +3 -0
- data/lib/iodine.rb +274 -0
- data/lib/rack/handler/iodine.rb +33 -0
- data/logo.png +0 -0
- metadata +284 -0
@@ -0,0 +1,752 @@
|
|
1
|
+
/*
|
2
|
+
copyright: Boaz Segev, 2016-2019
|
3
|
+
license: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#define FIO_INCLUDE_STR
|
8
|
+
#include <fio.h>
|
9
|
+
|
10
|
+
/* subscription lists have a long lifetime */
|
11
|
+
#define FIO_FORCE_MALLOC_TMP 1
|
12
|
+
#define FIO_INCLUDE_LINKED_LIST
|
13
|
+
#include <fio.h>
|
14
|
+
|
15
|
+
#include <fiobj.h>
|
16
|
+
|
17
|
+
#include <http.h>
|
18
|
+
#include <http_internal.h>
|
19
|
+
|
20
|
+
#ifndef __MINGW32__
|
21
|
+
#include <arpa/inet.h>
|
22
|
+
#endif
|
23
|
+
#include <errno.h>
|
24
|
+
#include <stdio.h>
|
25
|
+
#include <stdlib.h>
|
26
|
+
#include <string.h>
|
27
|
+
#include <strings.h>
|
28
|
+
|
29
|
+
#include <websocket_parser.h>
|
30
|
+
|
31
|
+
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
|
32
|
+
!defined(__MINGW32__)
|
33
|
+
#include <endian.h>
|
34
|
+
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
|
35
|
+
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
36
|
+
#define __BIG_ENDIAN__
|
37
|
+
#endif
|
38
|
+
#endif
|
39
|
+
|
40
|
+
/*******************************************************************************
|
41
|
+
Buffer management - update to change the way the buffer is handled.
|
42
|
+
*/
|
43
|
+
struct buffer_s {
|
44
|
+
void *data;
|
45
|
+
size_t size;
|
46
|
+
};
|
47
|
+
|
48
|
+
#pragma weak create_ws_buffer
|
49
|
+
/** returns a buffer_s struct, with a buffer (at least) `size` long. */
|
50
|
+
struct buffer_s create_ws_buffer(ws_s *owner);
|
51
|
+
|
52
|
+
#pragma weak resize_ws_buffer
|
53
|
+
/** returns a buffer_s struct, with a buffer (at least) `size` long. */
|
54
|
+
struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s);
|
55
|
+
|
56
|
+
#pragma weak free_ws_buffer
|
57
|
+
/** releases an existing buffer. */
|
58
|
+
void free_ws_buffer(ws_s *owner, struct buffer_s);
|
59
|
+
|
60
|
+
/** Sets the initial buffer size. (4Kb)*/
|
61
|
+
#define WS_INITIAL_BUFFER_SIZE 4096UL
|
62
|
+
|
63
|
+
/*******************************************************************************
|
64
|
+
Buffer management - simple implementation...
|
65
|
+
Since Websocket connections have a long life expectancy, optimizing this part of
|
66
|
+
the code probably wouldn't offer a high performance boost.
|
67
|
+
*/
|
68
|
+
|
69
|
+
// buffer increments by 4,096 Bytes (4Kb)
|
70
|
+
#define round_up_buffer_size(size) (((size) >> 12) + 1) << 12
|
71
|
+
|
72
|
+
struct buffer_s create_ws_buffer(ws_s *owner) {
|
73
|
+
(void)(owner);
|
74
|
+
struct buffer_s buff;
|
75
|
+
buff.size = WS_INITIAL_BUFFER_SIZE;
|
76
|
+
buff.data = malloc(buff.size);
|
77
|
+
FIO_ASSERT_ALLOC(buff.data);
|
78
|
+
return buff;
|
79
|
+
}
|
80
|
+
|
81
|
+
struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s buff) {
|
82
|
+
buff.size = round_up_buffer_size(buff.size);
|
83
|
+
void *tmp = realloc(buff.data, buff.size);
|
84
|
+
if (!tmp) {
|
85
|
+
free_ws_buffer(owner, buff);
|
86
|
+
buff.size = 0;
|
87
|
+
}
|
88
|
+
buff.data = tmp;
|
89
|
+
return buff;
|
90
|
+
}
|
91
|
+
void free_ws_buffer(ws_s *owner, struct buffer_s buff) {
|
92
|
+
(void)(owner);
|
93
|
+
free(buff.data);
|
94
|
+
}
|
95
|
+
|
96
|
+
#undef round_up_buffer_size
|
97
|
+
|
98
|
+
/*******************************************************************************
|
99
|
+
Create/Destroy the websocket object (prototypes)
|
100
|
+
*/
|
101
|
+
|
102
|
+
static ws_s *new_websocket();
|
103
|
+
static void destroy_ws(ws_s *ws);
|
104
|
+
|
105
|
+
/*******************************************************************************
|
106
|
+
The Websocket object (protocol + parser)
|
107
|
+
*/
|
108
|
+
struct ws_s {
|
109
|
+
/** The Websocket protocol */
|
110
|
+
fio_protocol_s protocol;
|
111
|
+
/** connection data */
|
112
|
+
intptr_t fd;
|
113
|
+
/** callbacks */
|
114
|
+
void (*on_message)(ws_s *ws, fio_str_info_s msg, uint8_t is_text);
|
115
|
+
void (*on_shutdown)(ws_s *ws);
|
116
|
+
void (*on_ready)(ws_s *ws);
|
117
|
+
void (*on_open)(ws_s *ws);
|
118
|
+
void (*on_close)(intptr_t uuid, void *udata);
|
119
|
+
/** Opaque user data. */
|
120
|
+
void *udata;
|
121
|
+
/** The maximum websocket message size */
|
122
|
+
size_t max_msg_size;
|
123
|
+
/** active pub/sub subscriptions */
|
124
|
+
fio_ls_s subscriptions;
|
125
|
+
fio_lock_i sub_lock;
|
126
|
+
/** socket buffer. */
|
127
|
+
struct buffer_s buffer;
|
128
|
+
/** data length (how much of the buffer actually used). */
|
129
|
+
size_t length;
|
130
|
+
/** total data length (including continuation frames). */
|
131
|
+
size_t total_length;
|
132
|
+
/** message buffer. */
|
133
|
+
FIOBJ msg;
|
134
|
+
/** latest text state. */
|
135
|
+
uint8_t is_text;
|
136
|
+
/** websocket connection type. */
|
137
|
+
uint8_t is_client;
|
138
|
+
};
|
139
|
+
|
140
|
+
/* *****************************************************************************
|
141
|
+
Create/Destroy the websocket subscription objects
|
142
|
+
***************************************************************************** */
|
143
|
+
|
144
|
+
static inline void clear_subscriptions(ws_s *ws) {
|
145
|
+
fio_lock(&ws->sub_lock);
|
146
|
+
while (fio_ls_any(&ws->subscriptions)) {
|
147
|
+
fio_unsubscribe(fio_ls_pop(&ws->subscriptions));
|
148
|
+
}
|
149
|
+
fio_unlock(&ws->sub_lock);
|
150
|
+
}
|
151
|
+
|
152
|
+
/* *****************************************************************************
|
153
|
+
Callbacks - Required functions for websocket_parser.h
|
154
|
+
***************************************************************************** */
|
155
|
+
|
156
|
+
static void websocket_on_unwrapped(void *ws_p, void *msg, uint64_t len,
|
157
|
+
char first, char last, char text,
|
158
|
+
unsigned char rsv) {
|
159
|
+
ws_s *ws = ws_p;
|
160
|
+
if (!ws)
|
161
|
+
return;
|
162
|
+
if (last && first) {
|
163
|
+
ws->on_message(ws, (fio_str_info_s){.data = msg, .len = len},
|
164
|
+
(uint8_t)text);
|
165
|
+
return;
|
166
|
+
}
|
167
|
+
if (ws->msg == FIOBJ_INVALID)
|
168
|
+
ws->msg = fiobj_str_buf(len);
|
169
|
+
ws->total_length += len;
|
170
|
+
if (first) {
|
171
|
+
ws->is_text = (uint8_t)text;
|
172
|
+
}
|
173
|
+
fiobj_str_write(ws->msg, msg, len);
|
174
|
+
if (last) {
|
175
|
+
ws->on_message(ws, fiobj_obj2cstr(ws->msg), ws->is_text);
|
176
|
+
fiobj_str_resize(ws->msg, 0);
|
177
|
+
ws->total_length = 0;
|
178
|
+
}
|
179
|
+
|
180
|
+
(void)rsv;
|
181
|
+
}
|
182
|
+
static void websocket_on_protocol_ping(void *ws_p, void *msg_, uint64_t len) {
|
183
|
+
ws_s *ws = ws_p;
|
184
|
+
if (msg_) {
|
185
|
+
void *buff = malloc(len + 16);
|
186
|
+
FIO_ASSERT_ALLOC(buff);
|
187
|
+
len = (((ws_s *)ws)->is_client
|
188
|
+
? websocket_client_wrap(buff, msg_, len, 10, 1, 1, 0)
|
189
|
+
: websocket_server_wrap(buff, msg_, len, 10, 1, 1, 0));
|
190
|
+
fio_write2(ws->fd, .data.buffer = buff, .length = len);
|
191
|
+
} else {
|
192
|
+
if (((ws_s *)ws)->is_client) {
|
193
|
+
fio_write2(ws->fd, .data.buffer = "\x8a\x80mask", .length = 6,
|
194
|
+
.after.dealloc = FIO_DEALLOC_NOOP);
|
195
|
+
} else {
|
196
|
+
fio_write2(ws->fd, .data.buffer = "\x8a\x00", .length = 2,
|
197
|
+
.after.dealloc = FIO_DEALLOC_NOOP);
|
198
|
+
}
|
199
|
+
}
|
200
|
+
FIO_LOG_DEBUG("Received ping and sent pong for Websocket %p (%d)", ws_p,
|
201
|
+
(int)(((ws_s *)ws_p)->fd));
|
202
|
+
}
|
203
|
+
static void websocket_on_protocol_pong(void *ws_p, void *msg, uint64_t len) {
|
204
|
+
FIO_LOG_DEBUG("Received pong for Websocket %p (%d)", ws_p,
|
205
|
+
(int)(((ws_s *)ws_p)->fd));
|
206
|
+
(void)len;
|
207
|
+
(void)msg;
|
208
|
+
(void)ws_p;
|
209
|
+
}
|
210
|
+
static void websocket_on_protocol_close(void *ws_p) {
|
211
|
+
ws_s *ws = ws_p;
|
212
|
+
fio_close(ws->fd);
|
213
|
+
}
|
214
|
+
static void websocket_on_protocol_error(void *ws_p) {
|
215
|
+
ws_s *ws = ws_p;
|
216
|
+
fio_close(ws->fd);
|
217
|
+
}
|
218
|
+
|
219
|
+
/*******************************************************************************
|
220
|
+
The Websocket Protocol implementation
|
221
|
+
*/
|
222
|
+
|
223
|
+
#define ws_protocol(fd) ((ws_s *)(server_get_protocol(fd)))
|
224
|
+
|
225
|
+
static void ws_ping(intptr_t fd, fio_protocol_s *ws) {
|
226
|
+
(void)(ws);
|
227
|
+
if (((ws_s *)ws)->is_client) {
|
228
|
+
fio_write2(fd, .data.buffer = "\x89\x80MASK", .length = 6,
|
229
|
+
.after.dealloc = FIO_DEALLOC_NOOP);
|
230
|
+
} else {
|
231
|
+
fio_write2(fd, .data.buffer = "\x89\x00", .length = 2,
|
232
|
+
.after.dealloc = FIO_DEALLOC_NOOP);
|
233
|
+
}
|
234
|
+
FIO_LOG_DEBUG("Sent ping for Websocket %p (%d)", (void *)ws, (int)fd);
|
235
|
+
}
|
236
|
+
|
237
|
+
static void on_close(intptr_t uuid, fio_protocol_s *_ws) {
|
238
|
+
destroy_ws((ws_s *)_ws);
|
239
|
+
(void)uuid;
|
240
|
+
}
|
241
|
+
|
242
|
+
static void on_ready(intptr_t fduuid, fio_protocol_s *ws) {
|
243
|
+
(void)(fduuid);
|
244
|
+
if (((ws_s *)ws)->on_ready)
|
245
|
+
((ws_s *)ws)->on_ready((ws_s *)ws);
|
246
|
+
}
|
247
|
+
|
248
|
+
static uint8_t on_shutdown(intptr_t fd, fio_protocol_s *ws) {
|
249
|
+
(void)(fd);
|
250
|
+
if (ws && ((ws_s *)ws)->on_shutdown)
|
251
|
+
((ws_s *)ws)->on_shutdown((ws_s *)ws);
|
252
|
+
if (((ws_s *)ws)->is_client) {
|
253
|
+
fio_write2(fd, .data.buffer = "\x88\x80MASK", .length = 6,
|
254
|
+
.after.dealloc = FIO_DEALLOC_NOOP);
|
255
|
+
} else {
|
256
|
+
fio_write2(fd, .data.buffer = "\x88\x00", .length = 2,
|
257
|
+
.after.dealloc = FIO_DEALLOC_NOOP);
|
258
|
+
}
|
259
|
+
return 0;
|
260
|
+
}
|
261
|
+
|
262
|
+
static void on_data(intptr_t sockfd, fio_protocol_s *ws_) {
|
263
|
+
ws_s *const ws = (ws_s *)ws_;
|
264
|
+
if (ws == NULL)
|
265
|
+
return;
|
266
|
+
struct websocket_packet_info_s info =
|
267
|
+
websocket_buffer_peek(ws->buffer.data, ws->length);
|
268
|
+
const uint64_t raw_length = info.packet_length + info.head_length;
|
269
|
+
/* test expected data amount */
|
270
|
+
if (ws->max_msg_size < raw_length + ws->total_length) {
|
271
|
+
/* too big */
|
272
|
+
websocket_close(ws);
|
273
|
+
return;
|
274
|
+
}
|
275
|
+
/* test buffer capacity */
|
276
|
+
if (raw_length > ws->buffer.size) {
|
277
|
+
ws->buffer.size = (size_t)raw_length;
|
278
|
+
ws->buffer = resize_ws_buffer(ws, ws->buffer);
|
279
|
+
if (!ws->buffer.data) {
|
280
|
+
// no memory.
|
281
|
+
websocket_close(ws);
|
282
|
+
return;
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
const ssize_t len = fio_read(sockfd, (uint8_t *)ws->buffer.data + ws->length,
|
287
|
+
ws->buffer.size - ws->length);
|
288
|
+
if (len <= 0) {
|
289
|
+
return;
|
290
|
+
}
|
291
|
+
ws->length = websocket_consume(ws->buffer.data, ws->length + len, ws,
|
292
|
+
(~(ws->is_client) & 1));
|
293
|
+
|
294
|
+
fio_force_event(sockfd, FIO_EVENT_ON_DATA);
|
295
|
+
}
|
296
|
+
|
297
|
+
static void on_data_first(intptr_t sockfd, fio_protocol_s *ws_) {
|
298
|
+
ws_s *const ws = (ws_s *)ws_;
|
299
|
+
if (ws->on_open)
|
300
|
+
ws->on_open(ws);
|
301
|
+
ws->protocol.on_data = on_data;
|
302
|
+
ws->protocol.on_ready = on_ready;
|
303
|
+
|
304
|
+
if (ws->length) {
|
305
|
+
ws->length = websocket_consume(ws->buffer.data, ws->length, ws,
|
306
|
+
(~(ws->is_client) & 1));
|
307
|
+
}
|
308
|
+
fio_force_event(sockfd, FIO_EVENT_ON_DATA);
|
309
|
+
fio_force_event(sockfd, FIO_EVENT_ON_READY);
|
310
|
+
}
|
311
|
+
|
312
|
+
/* later */
|
313
|
+
static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
|
314
|
+
char first, char last, char client);
|
315
|
+
|
316
|
+
/*******************************************************************************
|
317
|
+
Create/Destroy the websocket object
|
318
|
+
*/
|
319
|
+
|
320
|
+
static ws_s *new_websocket(intptr_t uuid) {
|
321
|
+
// allocate the protocol object
|
322
|
+
ws_s *ws = malloc(sizeof(*ws));
|
323
|
+
FIO_ASSERT_ALLOC(ws);
|
324
|
+
*ws = (ws_s){
|
325
|
+
.protocol.ping = ws_ping,
|
326
|
+
.protocol.on_data = on_data_first,
|
327
|
+
.protocol.on_close = on_close,
|
328
|
+
.protocol.on_ready = NULL /* filled in after `on_open` */,
|
329
|
+
.protocol.on_shutdown = on_shutdown,
|
330
|
+
.subscriptions = FIO_LS_INIT(ws->subscriptions),
|
331
|
+
.is_client = 0,
|
332
|
+
.fd = uuid,
|
333
|
+
};
|
334
|
+
return ws;
|
335
|
+
}
|
336
|
+
static void destroy_ws(ws_s *ws) {
|
337
|
+
if (ws->on_close)
|
338
|
+
ws->on_close(ws->fd, ws->udata);
|
339
|
+
if (ws->msg)
|
340
|
+
fiobj_free(ws->msg);
|
341
|
+
clear_subscriptions(ws);
|
342
|
+
free_ws_buffer(ws, ws->buffer);
|
343
|
+
free(ws);
|
344
|
+
}
|
345
|
+
|
346
|
+
void websocket_attach(intptr_t uuid, http_settings_s *http_settings,
|
347
|
+
websocket_settings_s *args, void *data, size_t length) {
|
348
|
+
ws_s *ws = new_websocket(uuid);
|
349
|
+
// we have an active websocket connection - prep the connection buffer
|
350
|
+
ws->buffer = create_ws_buffer(ws);
|
351
|
+
// Setup ws callbacks
|
352
|
+
ws->on_open = args->on_open;
|
353
|
+
ws->on_close = args->on_close;
|
354
|
+
ws->on_message = args->on_message;
|
355
|
+
ws->on_ready = args->on_ready;
|
356
|
+
ws->on_shutdown = args->on_shutdown;
|
357
|
+
// setup any user data
|
358
|
+
ws->udata = args->udata;
|
359
|
+
if (http_settings) {
|
360
|
+
// client mode?
|
361
|
+
ws->is_client = http_settings->is_client;
|
362
|
+
// buffer limits
|
363
|
+
ws->max_msg_size = http_settings->ws_max_msg_size;
|
364
|
+
// update the timeout
|
365
|
+
fio_timeout_set(uuid, http_settings->ws_timeout);
|
366
|
+
} else {
|
367
|
+
ws->max_msg_size = (1024 * 256);
|
368
|
+
fio_timeout_set(uuid, 40);
|
369
|
+
}
|
370
|
+
|
371
|
+
if (data && length) {
|
372
|
+
if (length > ws->buffer.size) {
|
373
|
+
ws->buffer.size = length;
|
374
|
+
ws->buffer = resize_ws_buffer(ws, ws->buffer);
|
375
|
+
if (!ws->buffer.data) {
|
376
|
+
// no memory.
|
377
|
+
fio_attach(uuid, (fio_protocol_s *)ws);
|
378
|
+
websocket_close(ws);
|
379
|
+
return;
|
380
|
+
}
|
381
|
+
}
|
382
|
+
memcpy(ws->buffer.data, data, length);
|
383
|
+
ws->length = length;
|
384
|
+
}
|
385
|
+
// update the protocol object, cleaning up the old one
|
386
|
+
fio_attach(uuid, (fio_protocol_s *)ws);
|
387
|
+
// allow the on_open and on_data to take over the control.
|
388
|
+
fio_force_event(uuid, FIO_EVENT_ON_DATA);
|
389
|
+
}
|
390
|
+
|
391
|
+
/*******************************************************************************
|
392
|
+
Writing to the Websocket
|
393
|
+
*/
|
394
|
+
#define WS_MAX_FRAME_SIZE \
|
395
|
+
(FIO_MEMORY_BLOCK_ALLOC_LIMIT - 4096) // should be less then `unsigned short`
|
396
|
+
|
397
|
+
static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
|
398
|
+
char first, char last, char client) {
|
399
|
+
if (len <= WS_MAX_FRAME_SIZE) {
|
400
|
+
void *buff = fio_malloc(len + 16);
|
401
|
+
FIO_ASSERT_ALLOC(buff);
|
402
|
+
len = (client ? websocket_client_wrap(buff, data, len, (text ? 1 : 2),
|
403
|
+
first, last, 0)
|
404
|
+
: websocket_server_wrap(buff, data, len, (text ? 1 : 2),
|
405
|
+
first, last, 0));
|
406
|
+
fio_write2(fd, .data.buffer = buff, .length = len,
|
407
|
+
.after.dealloc = fio_free);
|
408
|
+
} else {
|
409
|
+
/* frame fragmentation is better for large data then large frames */
|
410
|
+
while (len > WS_MAX_FRAME_SIZE) {
|
411
|
+
websocket_write_impl(fd, data, WS_MAX_FRAME_SIZE, text, first, 0, client);
|
412
|
+
data = ((uint8_t *)data) + WS_MAX_FRAME_SIZE;
|
413
|
+
first = 0;
|
414
|
+
len -= WS_MAX_FRAME_SIZE;
|
415
|
+
}
|
416
|
+
websocket_write_impl(fd, data, len, text, first, 1, client);
|
417
|
+
}
|
418
|
+
return;
|
419
|
+
}
|
420
|
+
|
421
|
+
/* *****************************************************************************
|
422
|
+
Multi-client broadcast optimizations
|
423
|
+
***************************************************************************** */
|
424
|
+
|
425
|
+
static void websocket_optimize_free(fio_msg_s *msg, void *metadata) {
|
426
|
+
fiobj_free((FIOBJ)metadata);
|
427
|
+
(void)msg;
|
428
|
+
}
|
429
|
+
|
430
|
+
static inline fio_msg_metadata_s websocket_optimize(fio_str_info_s msg,
|
431
|
+
unsigned char opcode) {
|
432
|
+
FIOBJ out = fiobj_str_buf(msg.len + 10);
|
433
|
+
fiobj_str_resize(out,
|
434
|
+
websocket_server_wrap(fiobj_obj2cstr(out).data, msg.data,
|
435
|
+
msg.len, opcode, 1, 1, 0));
|
436
|
+
fio_msg_metadata_s ret = {
|
437
|
+
.on_finish = websocket_optimize_free,
|
438
|
+
.metadata = (void *)out,
|
439
|
+
};
|
440
|
+
return ret;
|
441
|
+
}
|
442
|
+
static fio_msg_metadata_s websocket_optimize_generic(fio_str_info_s ch,
|
443
|
+
fio_str_info_s msg,
|
444
|
+
uint8_t is_json) {
|
445
|
+
fio_str_s tmp = FIO_STR_INIT_EXISTING(ch.data, ch.len, 0); // don't free
|
446
|
+
tmp.dealloc = NULL;
|
447
|
+
unsigned char opcode = 2;
|
448
|
+
if (tmp.len <= (2 << 19) && fio_str_utf8_valid(&tmp)) {
|
449
|
+
opcode = 1;
|
450
|
+
}
|
451
|
+
fio_msg_metadata_s ret = websocket_optimize(msg, opcode);
|
452
|
+
ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB;
|
453
|
+
return ret;
|
454
|
+
(void)ch;
|
455
|
+
(void)is_json;
|
456
|
+
}
|
457
|
+
|
458
|
+
static fio_msg_metadata_s websocket_optimize_text(fio_str_info_s ch,
|
459
|
+
fio_str_info_s msg,
|
460
|
+
uint8_t is_json) {
|
461
|
+
fio_msg_metadata_s ret = websocket_optimize(msg, 1);
|
462
|
+
ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB_TEXT;
|
463
|
+
return ret;
|
464
|
+
(void)ch;
|
465
|
+
(void)is_json;
|
466
|
+
}
|
467
|
+
|
468
|
+
static fio_msg_metadata_s websocket_optimize_binary(fio_str_info_s ch,
|
469
|
+
fio_str_info_s msg,
|
470
|
+
uint8_t is_json) {
|
471
|
+
fio_msg_metadata_s ret = websocket_optimize(msg, 2);
|
472
|
+
ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB_BINARY;
|
473
|
+
return ret;
|
474
|
+
(void)ch;
|
475
|
+
(void)is_json;
|
476
|
+
}
|
477
|
+
|
478
|
+
/**
|
479
|
+
* Enables (or disables) broadcast optimizations.
|
480
|
+
*
|
481
|
+
* When using WebSocket pub/sub system is originally optimized for either
|
482
|
+
* non-direct transmission (messages are handled by callbacks) or direct
|
483
|
+
* transmission to 1-3 clients per channel (on average), meaning that the
|
484
|
+
* majority of the messages are meant for a single recipient (or multiple
|
485
|
+
* callback recipients) and only some are expected to be directly transmitted to
|
486
|
+
* a group.
|
487
|
+
*
|
488
|
+
* However, when most messages are intended for direct transmission to more than
|
489
|
+
* 3 clients (on average), certain optimizations can be made to improve memory
|
490
|
+
* consumption (minimize duplication or WebSocket network data).
|
491
|
+
*
|
492
|
+
* This function allows enablement (or disablement) of these optimizations.
|
493
|
+
* These optimizations include:
|
494
|
+
*
|
495
|
+
* * WEBSOCKET_OPTIMIZE_PUBSUB - optimize all direct transmission messages,
|
496
|
+
* best attempt to detect Text vs. Binary data.
|
497
|
+
* * WEBSOCKET_OPTIMIZE_PUBSUB_TEXT - optimize direct pub/sub text messages.
|
498
|
+
* * WEBSOCKET_OPTIMIZE_PUBSUB_BINARY - optimize direct pub/sub binary messages.
|
499
|
+
*
|
500
|
+
* Note: to disable an optimization it should be disabled the same amount of
|
501
|
+
* times it was enabled - multiple optimization enablements for the same type
|
502
|
+
* are merged, but reference counted (disabled when reference is zero).
|
503
|
+
*/
|
504
|
+
void websocket_optimize4broadcasts(intptr_t type, int enable) {
|
505
|
+
static intptr_t generic = 0;
|
506
|
+
static intptr_t text = 0;
|
507
|
+
static intptr_t binary = 0;
|
508
|
+
fio_msg_metadata_s (*callback)(fio_str_info_s, fio_str_info_s, uint8_t);
|
509
|
+
intptr_t *counter;
|
510
|
+
switch ((0 - type)) {
|
511
|
+
case (0 - WEBSOCKET_OPTIMIZE_PUBSUB):
|
512
|
+
counter = &generic;
|
513
|
+
callback = websocket_optimize_generic;
|
514
|
+
break;
|
515
|
+
case (0 - WEBSOCKET_OPTIMIZE_PUBSUB_TEXT):
|
516
|
+
counter = &text;
|
517
|
+
callback = websocket_optimize_text;
|
518
|
+
break;
|
519
|
+
case (0 - WEBSOCKET_OPTIMIZE_PUBSUB_BINARY):
|
520
|
+
counter = &binary;
|
521
|
+
callback = websocket_optimize_binary;
|
522
|
+
break;
|
523
|
+
default:
|
524
|
+
return;
|
525
|
+
}
|
526
|
+
if (enable) {
|
527
|
+
if (fio_atomic_add(counter, 1) == 1) {
|
528
|
+
fio_message_metadata_callback_set(callback, 1);
|
529
|
+
}
|
530
|
+
} else {
|
531
|
+
if (fio_atomic_sub(counter, 1) == 0) {
|
532
|
+
fio_message_metadata_callback_set(callback, 0);
|
533
|
+
}
|
534
|
+
}
|
535
|
+
}
|
536
|
+
|
537
|
+
/* *****************************************************************************
|
538
|
+
Subscription handling
|
539
|
+
***************************************************************************** */
|
540
|
+
|
541
|
+
typedef struct {
|
542
|
+
void (*on_message)(ws_s *ws, fio_str_info_s channel, fio_str_info_s msg,
|
543
|
+
void *udata);
|
544
|
+
void (*on_unsubscribe)(void *udata);
|
545
|
+
void *udata;
|
546
|
+
} websocket_sub_data_s;
|
547
|
+
|
548
|
+
static inline void websocket_on_pubsub_message_direct_internal(fio_msg_s *msg,
|
549
|
+
uint8_t txt) {
|
550
|
+
fio_protocol_s *pr =
|
551
|
+
fio_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE);
|
552
|
+
if (!pr) {
|
553
|
+
if (errno == EBADF)
|
554
|
+
return;
|
555
|
+
fio_message_defer(msg);
|
556
|
+
return;
|
557
|
+
}
|
558
|
+
FIOBJ message = FIOBJ_INVALID;
|
559
|
+
FIOBJ pre_wrapped = FIOBJ_INVALID;
|
560
|
+
if (!((ws_s *)pr)->is_client) {
|
561
|
+
/* pre-wrapping is only for client data */
|
562
|
+
switch (txt) {
|
563
|
+
case 0:
|
564
|
+
pre_wrapped =
|
565
|
+
(FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB_BINARY);
|
566
|
+
break;
|
567
|
+
case 1:
|
568
|
+
pre_wrapped =
|
569
|
+
(FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB_TEXT);
|
570
|
+
break;
|
571
|
+
case 2:
|
572
|
+
pre_wrapped = (FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB);
|
573
|
+
break;
|
574
|
+
default:
|
575
|
+
break;
|
576
|
+
}
|
577
|
+
if (pre_wrapped) {
|
578
|
+
// FIO_LOG_DEBUG(
|
579
|
+
// "pub/sub WebSocket optimization route for pre-wrapped message.");
|
580
|
+
fiobj_send_free((intptr_t)msg->udata1, fiobj_dup(pre_wrapped));
|
581
|
+
goto finish;
|
582
|
+
}
|
583
|
+
}
|
584
|
+
if (txt == 2) {
|
585
|
+
/* unknown text state */
|
586
|
+
fio_str_s tmp =
|
587
|
+
FIO_STR_INIT_STATIC2(msg->msg.data, msg->msg.len); // don't free
|
588
|
+
txt = (tmp.len >= (2 << 14) ? 0 : fio_str_utf8_valid(&tmp));
|
589
|
+
}
|
590
|
+
websocket_write((ws_s *)pr, msg->msg, txt & 1);
|
591
|
+
fiobj_free(message);
|
592
|
+
finish:
|
593
|
+
fio_protocol_unlock(pr, FIO_PR_LOCK_WRITE);
|
594
|
+
}
|
595
|
+
|
596
|
+
static void websocket_on_pubsub_message_direct(fio_msg_s *msg) {
|
597
|
+
websocket_on_pubsub_message_direct_internal(msg, 2);
|
598
|
+
}
|
599
|
+
|
600
|
+
static void websocket_on_pubsub_message_direct_txt(fio_msg_s *msg) {
|
601
|
+
websocket_on_pubsub_message_direct_internal(msg, 1);
|
602
|
+
}
|
603
|
+
|
604
|
+
static void websocket_on_pubsub_message_direct_bin(fio_msg_s *msg) {
|
605
|
+
websocket_on_pubsub_message_direct_internal(msg, 0);
|
606
|
+
}
|
607
|
+
|
608
|
+
static void websocket_on_pubsub_message(fio_msg_s *msg) {
|
609
|
+
fio_protocol_s *pr =
|
610
|
+
fio_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_TASK);
|
611
|
+
if (!pr) {
|
612
|
+
if (errno == EBADF)
|
613
|
+
return;
|
614
|
+
fio_message_defer(msg);
|
615
|
+
return;
|
616
|
+
}
|
617
|
+
websocket_sub_data_s *d = msg->udata2;
|
618
|
+
|
619
|
+
if (d->on_message)
|
620
|
+
d->on_message((ws_s *)pr, msg->channel, msg->msg, d->udata);
|
621
|
+
fio_protocol_unlock(pr, FIO_PR_LOCK_TASK);
|
622
|
+
}
|
623
|
+
|
624
|
+
static void websocket_on_unsubscribe(void *u1, void *u2) {
|
625
|
+
websocket_sub_data_s *d = u2;
|
626
|
+
if (d->on_unsubscribe) {
|
627
|
+
d->on_unsubscribe(d->udata);
|
628
|
+
}
|
629
|
+
|
630
|
+
if ((intptr_t)d->on_message == (intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB) {
|
631
|
+
websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB, 0);
|
632
|
+
} else if ((intptr_t)d->on_message ==
|
633
|
+
(intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB_TEXT) {
|
634
|
+
websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB_TEXT, 0);
|
635
|
+
} else if ((intptr_t)d->on_message ==
|
636
|
+
(intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB_BINARY) {
|
637
|
+
websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB_BINARY, 0);
|
638
|
+
}
|
639
|
+
free(d);
|
640
|
+
(void)u1;
|
641
|
+
}
|
642
|
+
|
643
|
+
/**
|
644
|
+
* Returns a subscription ID on success and 0 on failure.
|
645
|
+
*/
|
646
|
+
#undef websocket_subscribe
|
647
|
+
uintptr_t websocket_subscribe(struct websocket_subscribe_s args) {
|
648
|
+
if (!args.ws || !fio_is_valid(args.ws->fd))
|
649
|
+
goto error;
|
650
|
+
websocket_sub_data_s *d = malloc(sizeof(*d));
|
651
|
+
FIO_ASSERT_ALLOC(d);
|
652
|
+
*d = (websocket_sub_data_s){
|
653
|
+
.udata = args.udata,
|
654
|
+
.on_message = args.on_message,
|
655
|
+
.on_unsubscribe = args.on_unsubscribe,
|
656
|
+
};
|
657
|
+
void (*handler)(fio_msg_s *) = websocket_on_pubsub_message;
|
658
|
+
if (!args.on_message) {
|
659
|
+
intptr_t br_type;
|
660
|
+
if (args.force_binary) {
|
661
|
+
br_type = WEBSOCKET_OPTIMIZE_PUBSUB_BINARY;
|
662
|
+
handler = websocket_on_pubsub_message_direct_bin;
|
663
|
+
} else if (args.force_text) {
|
664
|
+
br_type = WEBSOCKET_OPTIMIZE_PUBSUB_TEXT;
|
665
|
+
handler = websocket_on_pubsub_message_direct_txt;
|
666
|
+
} else {
|
667
|
+
br_type = WEBSOCKET_OPTIMIZE_PUBSUB;
|
668
|
+
handler = websocket_on_pubsub_message_direct;
|
669
|
+
}
|
670
|
+
websocket_optimize4broadcasts(br_type, 1);
|
671
|
+
d->on_message =
|
672
|
+
(void (*)(ws_s *, fio_str_info_s, fio_str_info_s, void *))br_type;
|
673
|
+
}
|
674
|
+
subscription_s *sub =
|
675
|
+
fio_subscribe(.channel = args.channel, .match = args.match,
|
676
|
+
.on_unsubscribe = websocket_on_unsubscribe,
|
677
|
+
.on_message = handler, .udata1 = (void *)args.ws->fd,
|
678
|
+
.udata2 = d);
|
679
|
+
if (!sub) {
|
680
|
+
/* don't free `d`, return (`d` freed by fio_subscribe) */
|
681
|
+
return 0;
|
682
|
+
}
|
683
|
+
fio_ls_s *pos;
|
684
|
+
fio_lock(&args.ws->sub_lock);
|
685
|
+
pos = fio_ls_push(&args.ws->subscriptions, sub);
|
686
|
+
fio_unlock(&args.ws->sub_lock);
|
687
|
+
|
688
|
+
return (uintptr_t)pos;
|
689
|
+
error:
|
690
|
+
if (args.on_unsubscribe)
|
691
|
+
args.on_unsubscribe(args.udata);
|
692
|
+
return 0;
|
693
|
+
}
|
694
|
+
|
695
|
+
/**
|
696
|
+
* Unsubscribes from a channel.
|
697
|
+
*/
|
698
|
+
void websocket_unsubscribe(ws_s *ws, uintptr_t subscription_id) {
|
699
|
+
fio_unsubscribe((subscription_s *)((fio_ls_s *)subscription_id)->obj);
|
700
|
+
fio_lock(&ws->sub_lock);
|
701
|
+
fio_ls_remove((fio_ls_s *)subscription_id);
|
702
|
+
fio_unlock(&ws->sub_lock);
|
703
|
+
|
704
|
+
(void)ws;
|
705
|
+
}
|
706
|
+
|
707
|
+
/*******************************************************************************
|
708
|
+
The API implementation
|
709
|
+
*/
|
710
|
+
|
711
|
+
/** Returns the opaque user data associated with the websocket. */
|
712
|
+
void *websocket_udata_get(ws_s *ws) { return ws->udata; }
|
713
|
+
|
714
|
+
/** Returns the the process specific connection's UUID (see `libsock`). */
|
715
|
+
intptr_t websocket_uuid(ws_s *ws) { return ws->fd; }
|
716
|
+
|
717
|
+
/** Sets the opaque user data associated with the websocket.
|
718
|
+
* Returns the old value, if any. */
|
719
|
+
void *websocket_udata_set(ws_s *ws, void *udata) {
|
720
|
+
void *old = ws->udata;
|
721
|
+
ws->udata = udata;
|
722
|
+
return old;
|
723
|
+
}
|
724
|
+
|
725
|
+
/**
|
726
|
+
* Returns 1 if the WebSocket connection is in Client mode (connected to a
|
727
|
+
* remote server) and 0 if the connection is in Server mode (a connection
|
728
|
+
* established using facil.io's HTTP server).
|
729
|
+
*/
|
730
|
+
uint8_t websocket_is_client(ws_s *ws) { return ws->is_client; }
|
731
|
+
|
732
|
+
/** Writes data to the websocket. Returns -1 on failure (0 on success). */
|
733
|
+
int websocket_write(ws_s *ws, fio_str_info_s msg, uint8_t is_text) {
|
734
|
+
if (fio_is_valid(ws->fd)) {
|
735
|
+
websocket_write_impl(ws->fd, msg.data, msg.len, is_text, 1, 1,
|
736
|
+
ws->is_client);
|
737
|
+
return 0;
|
738
|
+
}
|
739
|
+
return -1;
|
740
|
+
}
|
741
|
+
/** Closes a websocket connection. */
|
742
|
+
void websocket_close(ws_s *ws) {
|
743
|
+
if (ws->is_client) {
|
744
|
+
fio_write2(ws->fd, .data.buffer = "\x88\x80MASK", .length = 6,
|
745
|
+
.after.dealloc = FIO_DEALLOC_NOOP);
|
746
|
+
} else {
|
747
|
+
fio_write2(ws->fd, .data.buffer = "\x88\x00", .length = 2,
|
748
|
+
.after.dealloc = FIO_DEALLOC_NOOP);
|
749
|
+
}
|
750
|
+
fio_close(ws->fd);
|
751
|
+
return;
|
752
|
+
}
|