iodine 0.3.6 → 0.4.0
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 +46 -0
- data/LIMITS.md +25 -0
- data/README.md +39 -80
- data/SPEC-Websocket-Draft.md +129 -4
- data/bin/echo +2 -2
- data/bin/http-hello +1 -0
- data/bin/updated api +113 -0
- data/bin/ws-echo +0 -1
- data/examples/broadcast.ru +56 -0
- data/examples/echo.ru +57 -0
- data/examples/hello.ru +30 -0
- data/examples/redis.ru +69 -0
- data/examples/shootout.ru +53 -0
- data/exe/iodine +2 -80
- data/ext/iodine/defer.c +11 -5
- data/ext/iodine/empty.h +26 -0
- data/ext/iodine/evio.h +1 -1
- data/ext/iodine/facil.c +103 -61
- data/ext/iodine/facil.h +20 -12
- data/ext/iodine/fio_dict.c +446 -0
- data/ext/iodine/fio_dict.h +90 -0
- data/ext/iodine/fio_hash_table.h +370 -0
- data/ext/iodine/fio_list.h +30 -3
- data/ext/iodine/http.c +169 -37
- data/ext/iodine/http.h +33 -10
- data/ext/iodine/http1.c +78 -42
- data/ext/iodine/http_request.c +6 -0
- data/ext/iodine/http_request.h +3 -0
- data/ext/iodine/http_response.c +43 -11
- data/ext/iodine/iodine.c +380 -0
- data/ext/iodine/iodine.h +62 -0
- data/ext/iodine/iodine_helpers.c +235 -0
- data/ext/iodine/iodine_helpers.h +13 -0
- data/ext/iodine/iodine_http.c +409 -241
- data/ext/iodine/iodine_http.h +7 -14
- data/ext/iodine/iodine_protocol.c +626 -0
- data/ext/iodine/iodine_protocol.h +13 -0
- data/ext/iodine/iodine_pubsub.c +646 -0
- data/ext/iodine/iodine_pubsub.h +27 -0
- data/ext/iodine/iodine_websockets.c +796 -0
- data/ext/iodine/iodine_websockets.h +19 -0
- data/ext/iodine/pubsub.c +544 -0
- data/ext/iodine/pubsub.h +215 -0
- data/ext/iodine/random.c +4 -4
- data/ext/iodine/rb-call.c +1 -5
- data/ext/iodine/rb-defer.c +3 -20
- data/ext/iodine/rb-rack-io.c +22 -22
- data/ext/iodine/rb-rack-io.h +3 -4
- data/ext/iodine/rb-registry.c +111 -118
- data/ext/iodine/redis_connection.c +277 -0
- data/ext/iodine/redis_connection.h +77 -0
- data/ext/iodine/redis_engine.c +398 -0
- data/ext/iodine/redis_engine.h +68 -0
- data/ext/iodine/resp.c +842 -0
- data/ext/iodine/resp.h +253 -0
- data/ext/iodine/sock.c +26 -12
- data/ext/iodine/sock.h +14 -3
- data/ext/iodine/spnlock.inc +19 -2
- data/ext/iodine/websockets.c +299 -11
- data/ext/iodine/websockets.h +159 -6
- data/lib/iodine.rb +104 -1
- data/lib/iodine/cli.rb +106 -0
- data/lib/iodine/monkeypatch.rb +40 -0
- data/lib/iodine/pubsub.rb +70 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/iodine/websocket.rb +12 -0
- data/lib/rack/handler/iodine.rb +33 -7
- metadata +35 -7
- data/ext/iodine/iodine_core.c +0 -760
- data/ext/iodine/iodine_core.h +0 -79
- data/ext/iodine/iodine_websocket.c +0 -551
- data/ext/iodine/iodine_websocket.h +0 -22
- data/lib/iodine/http.rb +0 -4
data/ext/iodine/resp.h
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz segev, 2017
|
3
|
+
License: MIT except for any non-public-domain algorithms (none that I'm aware
|
4
|
+
of), which might be subject to their own licenses.
|
5
|
+
|
6
|
+
Feel free to copy, use and enjoy in accordance with to the license(s).
|
7
|
+
*/
|
8
|
+
#ifndef H_RESP_PARSER_H
|
9
|
+
/**
|
10
|
+
This is a neive implementation of the RESP protocol for Redis.
|
11
|
+
*/
|
12
|
+
#define H_RESP_PARSER_H
|
13
|
+
|
14
|
+
#include <stdint.h>
|
15
|
+
#include <stdlib.h>
|
16
|
+
|
17
|
+
/** The RESP Parser Type */
|
18
|
+
typedef struct resp_parser_s *resp_parser_pt;
|
19
|
+
|
20
|
+
/* *****************************************************************************
|
21
|
+
RESP types and objects (Arrays, Strings & Integers)
|
22
|
+
***************************************************************************** */
|
23
|
+
|
24
|
+
enum resp_type_enum {
|
25
|
+
/** A simple flag object object (`resp_object_s`) for NULL. */
|
26
|
+
RESP_NULL = 0,
|
27
|
+
/** A simple flag object object (`resp_object_s`) for OK. */
|
28
|
+
RESP_OK = 1,
|
29
|
+
/** A String object (`resp_string_s`) that indicates an error. */
|
30
|
+
RESP_ERR = (2 + 8),
|
31
|
+
/** A Number object object (`resp_number_s`). */
|
32
|
+
RESP_NUMBER = 4,
|
33
|
+
/** A String object (`resp_string_s`). */
|
34
|
+
RESP_STRING = 8,
|
35
|
+
/** An Array object object (`resp_array_s`). */
|
36
|
+
RESP_ARRAY = 16,
|
37
|
+
/**
|
38
|
+
* A specific Array object object (`resp_array_s`) for Pub/Sub semantics.
|
39
|
+
*
|
40
|
+
* This is more of a hint than a decree, sometimes pubsub semantics are
|
41
|
+
* misleading.
|
42
|
+
*/
|
43
|
+
RESP_PUBSUB = (32 + 16),
|
44
|
+
};
|
45
|
+
|
46
|
+
/** a simple emtpy object type, used for RESP_NULL and RESP_OK */
|
47
|
+
typedef struct { enum resp_type_enum type; } resp_object_s;
|
48
|
+
|
49
|
+
/** The RESP_ARRAY and RESP_PUBSUB types */
|
50
|
+
typedef struct {
|
51
|
+
enum resp_type_enum type;
|
52
|
+
size_t len;
|
53
|
+
size_t pos; /** allows simple iteration. */
|
54
|
+
resp_object_s *array[];
|
55
|
+
} resp_array_s;
|
56
|
+
|
57
|
+
/** The RESP_STRING and RESP_ERR types */
|
58
|
+
typedef struct {
|
59
|
+
enum resp_type_enum type;
|
60
|
+
size_t len;
|
61
|
+
uint8_t string[];
|
62
|
+
} resp_string_s;
|
63
|
+
|
64
|
+
/** The RESP_NUMBER type */
|
65
|
+
typedef struct {
|
66
|
+
enum resp_type_enum type;
|
67
|
+
int64_t number;
|
68
|
+
} resp_number_s;
|
69
|
+
|
70
|
+
#define resp_obj2arr(obj) \
|
71
|
+
((resp_array_s *)((obj)->type == RESP_ARRAY || (obj)->type == RESP_PUBSUB \
|
72
|
+
? (obj) \
|
73
|
+
: NULL))
|
74
|
+
#define resp_obj2str(obj) \
|
75
|
+
((resp_string_s *)((obj)->type == RESP_STRING || (obj)->type == RESP_ERR \
|
76
|
+
? (obj) \
|
77
|
+
: NULL))
|
78
|
+
#define resp_obj2num(obj) \
|
79
|
+
((resp_number_s *)((obj)->type == RESP_NUMBER ? (obj) : NULL))
|
80
|
+
|
81
|
+
/** Allocates an RESP NULL objcet. Remeber to free when done. */
|
82
|
+
resp_object_s *resp_nil2obj(void);
|
83
|
+
|
84
|
+
/** Allocates an RESP OK objcet. Remeber to free when done. */
|
85
|
+
resp_object_s *resp_OK2obj(void);
|
86
|
+
|
87
|
+
/** Allocates an RESP Error objcet. Remeber to free when done. */
|
88
|
+
resp_object_s *resp_err2obj(const void *msg, size_t len);
|
89
|
+
|
90
|
+
/** Allocates an RESP Number objcet. Remeber to free when done. */
|
91
|
+
resp_object_s *resp_num2obj(uint64_t num);
|
92
|
+
|
93
|
+
/** Allocates an RESP String objcet. Remeber to free when done. */
|
94
|
+
resp_object_s *resp_str2obj(const void *str, size_t len);
|
95
|
+
|
96
|
+
/**
|
97
|
+
*Allocates an RESP Array objcet. Remeber to free when done (freeing an array
|
98
|
+
*frees it's children automatically).
|
99
|
+
*
|
100
|
+
* It's possible to pass NULL as the `argv`, in which case the array created
|
101
|
+
* will have the capacity `argc` and could me manually populated.
|
102
|
+
*
|
103
|
+
* The objects are MOVED into the array's possesion. If you wish to retain a
|
104
|
+
* copy of the objects, use the `resp_dup_object` to increase their reference
|
105
|
+
* count.
|
106
|
+
*/
|
107
|
+
resp_object_s *resp_arr2obj(int argc, resp_object_s *argv[]);
|
108
|
+
|
109
|
+
/** Duplicates an object by increasing it's reference count. */
|
110
|
+
resp_object_s *resp_dup_object(resp_object_s *obj);
|
111
|
+
|
112
|
+
/** frees an object by decreasing it's reference count and testing. */
|
113
|
+
void resp_free_object(resp_object_s *obj);
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Formats a RESP object back into a string.
|
117
|
+
*
|
118
|
+
* Returns 0 on success and -1 on failur.
|
119
|
+
*
|
120
|
+
* Accepts a memory buffer `dest` to which the data will be written and a poiner
|
121
|
+
* to the size of the buffer.
|
122
|
+
*
|
123
|
+
* Once the function returns, `size` will be updated to include the number of
|
124
|
+
* bytes required for the string. If the function returned a failuer, this value
|
125
|
+
* can be used to allocate enough memory to contain the string.
|
126
|
+
*
|
127
|
+
* The string is Binary safe and it ISN'T always NUL terminated.
|
128
|
+
*
|
129
|
+
* The optional `parser` argument allows experimental extensions to be used when
|
130
|
+
* formatting the object. It can be ignored when formatting without extensions.
|
131
|
+
*
|
132
|
+
* **If implementing a server**: DON'T use this to format outgoing pub/sub
|
133
|
+
* notifications.
|
134
|
+
*
|
135
|
+
* When implementing a server, Pub/Sub should avoid multiple copies by using a
|
136
|
+
* dedicated buffer with a reference count. By decreasing the reference count
|
137
|
+
* every time the message was sent (see the `sock_write2` support for the
|
138
|
+
* dealloc callback), it's possible to avoid multiple copies of the message.
|
139
|
+
*/
|
140
|
+
int resp_format(resp_parser_pt p, uint8_t *dest, size_t *size,
|
141
|
+
resp_object_s *obj);
|
142
|
+
|
143
|
+
/**
|
144
|
+
* Performs a task on each object. Protects from loop-backs.
|
145
|
+
*
|
146
|
+
* To break loop in the middle, `task` can return -1.
|
147
|
+
*
|
148
|
+
* Returns count.
|
149
|
+
*/
|
150
|
+
size_t resp_obj_each(resp_parser_pt p, resp_object_s *obj,
|
151
|
+
int (*task)(resp_parser_pt p, resp_object_s *obj,
|
152
|
+
void *arg),
|
153
|
+
void *arg);
|
154
|
+
|
155
|
+
/* *****************************************************************************
|
156
|
+
The RESP Parser
|
157
|
+
***************************************************************************** */
|
158
|
+
|
159
|
+
/** create the parser */
|
160
|
+
resp_parser_pt resp_parser_new(void);
|
161
|
+
|
162
|
+
/** free the parser and it's resources. */
|
163
|
+
void resp_parser_destroy(resp_parser_pt);
|
164
|
+
|
165
|
+
/** Clears the parser state, as if starting a new session / connection. */
|
166
|
+
void resp_parser_clear(resp_parser_pt);
|
167
|
+
|
168
|
+
/**
|
169
|
+
* Feed the parser with data.
|
170
|
+
*
|
171
|
+
* Returns any fully parsed object / reply (often an array, but not always) or
|
172
|
+
* NULL (needs more data / error).
|
173
|
+
*
|
174
|
+
* If a RESP object was parsed, it is returned and `len` is updated to reflect
|
175
|
+
* the number of bytes actually read.
|
176
|
+
*
|
177
|
+
* If more data is needed, NULL is returned and `len` is left unchanged.
|
178
|
+
*
|
179
|
+
* An error is reported by by returning NULL and setting `len` to 0 at the same
|
180
|
+
* time.
|
181
|
+
*
|
182
|
+
* Partial consumption is possible when multiple replys were available in the
|
183
|
+
* buffer. Otherwise the parser will consume the whole of the buffer.
|
184
|
+
*
|
185
|
+
*/
|
186
|
+
resp_object_s *resp_parser_feed(resp_parser_pt, uint8_t *buffer, size_t *len);
|
187
|
+
|
188
|
+
/* *****************************************************************************
|
189
|
+
State - The Pub / Sub Multiplexer (Experimental)
|
190
|
+
***************************************************************************** */
|
191
|
+
|
192
|
+
/**
|
193
|
+
It seems to me that the main reason that pub/sub messages and normal RESP
|
194
|
+
connetcions cannot share the same socket is the risk of identity collisions.
|
195
|
+
|
196
|
+
For example, the command LRANGE might return the following array response
|
197
|
+
`["message", "users", "hello"]`... this looks exactly like a Pub/Sub
|
198
|
+
notification.
|
199
|
+
|
200
|
+
However, this situation is very uncomfortable. For example:
|
201
|
+
|
202
|
+
* The sender already knows the content of the message. There is no reason to
|
203
|
+
waste bandwidth to send the same message back to the sender using a different
|
204
|
+
socket.
|
205
|
+
|
206
|
+
* The cost isn't just Bandwidth, but also memory, since the sender will have two
|
207
|
+
copies of the same message (if not more), the one being sent and the other
|
208
|
+
being received (sometimes more then once, for different channel patterns).
|
209
|
+
|
210
|
+
|
211
|
+
* Instead of handling the message localy, the sender is forced to wait until the
|
212
|
+
message is received by the pub/sub Redis conection - otherwise tere will be
|
213
|
+
duplicate messages being published.
|
214
|
+
|
215
|
+
* This design doubles the client load (number of connections) for each Redis
|
216
|
+
server (and client).
|
217
|
+
|
218
|
+
But we can solve this.
|
219
|
+
|
220
|
+
For example, what if we use a "magic byte" to distinguish between the array
|
221
|
+
`["message", "users", "hello"]` and the pub/sub notification `["message",
|
222
|
+
"users", "hello"]`?
|
223
|
+
|
224
|
+
What if every time the first word in an array response satrts with an `"m"` or a
|
225
|
+
`"+"`, we will add the `"+"` byte infront of it?
|
226
|
+
|
227
|
+
Now the notification will look like this: `["message", "users", "hello"]`, and
|
228
|
+
the array response (i.e. to `LRANGE`) will be: `["+message", "users", "hello"]`
|
229
|
+
- a distinct difference allowing for pub/sub and regular conections to use the
|
230
|
+
same pipelining socket.
|
231
|
+
|
232
|
+
The big issue (and I may be wrong), is backwards compatibility - we can't change
|
233
|
+
the semantics of the protocol without breaking existing clients... or can we?
|
234
|
+
|
235
|
+
I believe this hurdle can be easily circumvented by adding a single command to
|
236
|
+
the existing pallet. Somthing along the lines of: `ENABLE <feature>`.
|
237
|
+
|
238
|
+
Such a flexible command allows clients to negotiate changes to the semantics
|
239
|
+
for the connection. It's meant to be a single non-reversible change for the
|
240
|
+
specific connection (similar to the `Upgrade` HTTP/Websocket concept).
|
241
|
+
|
242
|
+
Now, the little `"+"` "magic byte" can be easily handled.
|
243
|
+
|
244
|
+
The following function activates the "magic byte" assuming the `ENABLE` command
|
245
|
+
was negotiated for the connection.
|
246
|
+
*/
|
247
|
+
void resp_enable_duplex_pubsub(resp_parser_pt parser);
|
248
|
+
|
249
|
+
#ifdef DEBUG
|
250
|
+
void resp_test(void);
|
251
|
+
#endif
|
252
|
+
|
253
|
+
#endif
|
data/ext/iodine/sock.c
CHANGED
@@ -383,15 +383,23 @@ Writing - from files
|
|
383
383
|
struct sock_packet_file_data_s {
|
384
384
|
intptr_t fd;
|
385
385
|
off_t offset;
|
386
|
-
|
386
|
+
union {
|
387
|
+
void (*close)(intptr_t);
|
388
|
+
void (*dealloc)(void *);
|
389
|
+
};
|
390
|
+
int *pfd;
|
387
391
|
uint8_t buffer[];
|
388
392
|
};
|
389
393
|
|
390
394
|
static void sock_perform_close_fd(intptr_t fd) { close(fd); }
|
395
|
+
static void sock_perform_close_pfd(void *pfd) { close(*(int *)pfd); }
|
391
396
|
|
392
397
|
static void sock_close_from_fd(packet_s *packet) {
|
393
398
|
struct sock_packet_file_data_s *ext = (void *)packet->buffer.buf;
|
394
|
-
|
399
|
+
if (ext->pfd)
|
400
|
+
ext->dealloc(ext->pfd);
|
401
|
+
else
|
402
|
+
ext->close(ext->fd);
|
395
403
|
}
|
396
404
|
|
397
405
|
static int sock_write_from_fd(int fd, struct packet_s *packet) {
|
@@ -871,10 +879,9 @@ ssize_t sock_read(intptr_t uuid, void *buf, size_t count) {
|
|
871
879
|
*/
|
872
880
|
ssize_t sock_write2_fn(sock_write_info_s options) {
|
873
881
|
int fd = sock_uuid2fd(options.uuid);
|
874
|
-
|
875
|
-
clear_fd(fd, 0);
|
882
|
+
|
876
883
|
// avoid work when an error is expected to occur.
|
877
|
-
if (!fdinfo(fd).open || options.offset < 0) {
|
884
|
+
if (validate_uuid(options.uuid) || !fdinfo(fd).open || options.offset < 0) {
|
878
885
|
if (options.move == 0) {
|
879
886
|
errno = (options.offset < 0) ? ERANGE : EBADF;
|
880
887
|
return -1;
|
@@ -887,12 +894,11 @@ ssize_t sock_write2_fn(sock_write_info_s options) {
|
|
887
894
|
errno = (options.offset < 0) ? ERANGE : EBADF;
|
888
895
|
return -1;
|
889
896
|
}
|
890
|
-
|
891
|
-
// options.offset = 0;
|
897
|
+
|
892
898
|
packet_s *packet = sock_packet_grab();
|
893
899
|
packet->buffer.len = options.length;
|
894
|
-
if (options.is_fd == 0) {
|
895
|
-
if (options.move == 0) {
|
900
|
+
if (options.is_fd == 0 && options.is_pfd == 0) { /* is data */
|
901
|
+
if (options.move == 0) { /* memory is copied. */
|
896
902
|
if (options.length <= BUFFER_PACKET_SIZE) {
|
897
903
|
/* small enough for internal buffer */
|
898
904
|
memcpy(packet->buffer.buf, (uint8_t *)options.buffer + options.offset,
|
@@ -915,11 +921,18 @@ ssize_t sock_write2_fn(sock_write_info_s options) {
|
|
915
921
|
ext->dealloc = options.dealloc ? options.dealloc : free;
|
916
922
|
packet->metadata = (struct packet_metadata_s){
|
917
923
|
.write_func = sock_write_buffer_ext, .free_func = sock_free_buffer_ext};
|
924
|
+
|
918
925
|
} else { /* is file */
|
919
926
|
struct sock_packet_file_data_s *ext = (void *)packet->buffer.buf;
|
920
|
-
|
921
|
-
|
922
|
-
|
927
|
+
if (options.is_pfd) {
|
928
|
+
ext->pfd = (int *)options.buffer;
|
929
|
+
ext->fd = *ext->pfd;
|
930
|
+
ext->dealloc = options.dealloc ? options.dealloc : sock_perform_close_pfd;
|
931
|
+
} else {
|
932
|
+
ext->fd = options.data_fd;
|
933
|
+
ext->pfd = NULL;
|
934
|
+
ext->close = options.close ? options.close : sock_perform_close_fd;
|
935
|
+
}
|
923
936
|
ext->offset = options.offset;
|
924
937
|
packet->metadata = (struct packet_metadata_s){
|
925
938
|
.write_func =
|
@@ -929,6 +942,7 @@ ssize_t sock_write2_fn(sock_write_info_s options) {
|
|
929
942
|
.free_func = options.move ? sock_close_from_fd
|
930
943
|
: (void (*)(packet_s *))SOCK_DEALLOC_NOOP};
|
931
944
|
}
|
945
|
+
|
932
946
|
/* place packet in queue */
|
933
947
|
place_packet_in_queue:
|
934
948
|
if (validate_uuid(options.uuid))
|
data/ext/iodine/sock.h
CHANGED
@@ -268,7 +268,7 @@ typedef struct {
|
|
268
268
|
/** This is an alternative deallocation callback accessor (same memory space
|
269
269
|
* as `dealloc`) for conveniently setting the file `close` callback.
|
270
270
|
*/
|
271
|
-
void (*close)(
|
271
|
+
void (*close)(intptr_t fd);
|
272
272
|
};
|
273
273
|
/** The length (size) of the buffer, or the amount of data to be sent from the
|
274
274
|
* file descriptor.
|
@@ -283,10 +283,21 @@ typedef struct {
|
|
283
283
|
unsigned move : 1;
|
284
284
|
/** The packet will be sent as soon as possible. */
|
285
285
|
unsigned urgent : 1;
|
286
|
-
/**
|
287
|
-
*
|
286
|
+
/**
|
287
|
+
* The buffer contains the value of a file descriptor (`int`). i.e.:
|
288
|
+
* `.data_fd = fd` or `.buffer = (void*)fd;`
|
288
289
|
*/
|
289
290
|
unsigned is_fd : 1;
|
291
|
+
/**
|
292
|
+
* The buffer **points** to a file descriptor (`int`): `.buffer = (void*)&fd;`
|
293
|
+
*
|
294
|
+
* In the case the `dealloc` function will be called, allowing both closure
|
295
|
+
* and deallocation of the `int` pointer.
|
296
|
+
*
|
297
|
+
* This feature can be used for file reference counting, such as implemented
|
298
|
+
* by the `fio_file_cache` service.
|
299
|
+
*/
|
300
|
+
unsigned is_pfd : 1;
|
290
301
|
/** for internal use */
|
291
302
|
unsigned rsv : 1;
|
292
303
|
} sock_write_info_s;
|
data/ext/iodine/spnlock.inc
CHANGED
@@ -43,15 +43,32 @@ typedef volatile unsigned char spn_lock_i;
|
|
43
43
|
|
44
44
|
/* Select the correct compiler builtin method. */
|
45
45
|
#if defined(__has_builtin)
|
46
|
-
|
47
|
-
#
|
46
|
+
|
47
|
+
#if __has_builtin(__atomic_exchange_n)
|
48
|
+
#define SPN_LOCK_BUILTIN(...) __atomic_exchange_n(__VA_ARGS__, __ATOMIC_ACQ_REL)
|
49
|
+
/** An atomic addition operation */
|
50
|
+
#define spn_add(...) __atomic_add_fetch(__VA_ARGS__, __ATOMIC_ACQ_REL)
|
51
|
+
/** An atomic subtraction operation */
|
52
|
+
#define spn_sub(...) __atomic_sub_fetch(__VA_ARGS__, __ATOMIC_ACQ_REL)
|
53
|
+
|
48
54
|
#elif __has_builtin(__sync_fetch_and_or)
|
49
55
|
#define SPN_LOCK_BUILTIN(...) __sync_fetch_and_or(__VA_ARGS__)
|
56
|
+
/** An atomic addition operation */
|
57
|
+
#define spn_add(...) __sync_add_and_fetch(__VA_ARGS__)
|
58
|
+
/** An atomic subtraction operation */
|
59
|
+
#define spn_sub(...) __sync_sub_and_fetch(__VA_ARGS__)
|
60
|
+
|
50
61
|
#else
|
51
62
|
#error Required builtin "__sync_swap" or "__sync_fetch_and_or" missing from compiler.
|
52
63
|
#endif /* defined(__has_builtin) */
|
64
|
+
|
53
65
|
#elif __GNUC__ > 3
|
54
66
|
#define SPN_LOCK_BUILTIN(...) __sync_fetch_and_or(__VA_ARGS__)
|
67
|
+
/** An atomic addition operation */
|
68
|
+
#define spn_add(...) __sync_add_and_fetch(__VA_ARGS__)
|
69
|
+
/** An atomic subtraction operation */
|
70
|
+
#define spn_sub(...) __sync_sub_and_fetch(__VA_ARGS__)
|
71
|
+
|
55
72
|
#else
|
56
73
|
#error Required builtin "__sync_swap" or "__sync_fetch_and_or" not found.
|
57
74
|
#endif
|
data/ext/iodine/websockets.c
CHANGED
@@ -6,7 +6,10 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
6
6
|
*/
|
7
7
|
#include "spnlock.inc"
|
8
8
|
|
9
|
+
#include "fio_list.h"
|
10
|
+
|
9
11
|
#include "bscrypt.h"
|
12
|
+
#include "pubsub.h"
|
10
13
|
#include "websockets.h"
|
11
14
|
#include <arpa/inet.h>
|
12
15
|
#include <errno.h>
|
@@ -106,10 +109,14 @@ struct Websocket {
|
|
106
109
|
void *udata;
|
107
110
|
/** The maximum websocket message size */
|
108
111
|
size_t max_msg_size;
|
112
|
+
/** active pub/sub subscriptions */
|
113
|
+
fio_list_s subscriptions;
|
109
114
|
/** message buffer. */
|
110
115
|
struct buffer_s buffer;
|
111
116
|
/** message length (how much of the buffer actually used). */
|
112
117
|
size_t length;
|
118
|
+
/** for fragmenting the `on_data` parsing and message handling. */
|
119
|
+
size_t resume_from;
|
113
120
|
/** parser. */
|
114
121
|
struct {
|
115
122
|
union {
|
@@ -156,6 +163,35 @@ static __thread struct {
|
|
156
163
|
char buffer[WEBSOCKET_READ_MAX];
|
157
164
|
} read_buffer;
|
158
165
|
|
166
|
+
/* *****************************************************************************
|
167
|
+
Create/Destroy the websocket subscription objects
|
168
|
+
***************************************************************************** */
|
169
|
+
|
170
|
+
typedef struct {
|
171
|
+
fio_list_s node;
|
172
|
+
pubsub_sub_pt sub;
|
173
|
+
} subscription_s;
|
174
|
+
|
175
|
+
static inline subscription_s *create_subscription(ws_s *ws, pubsub_sub_pt sub) {
|
176
|
+
subscription_s *s = malloc(sizeof(*s));
|
177
|
+
s->sub = sub;
|
178
|
+
fio_list_add(&ws->subscriptions, &s->node);
|
179
|
+
return s;
|
180
|
+
}
|
181
|
+
|
182
|
+
static inline void free_subscription(subscription_s *s) {
|
183
|
+
fio_list_remove(&s->node);
|
184
|
+
free(s);
|
185
|
+
}
|
186
|
+
|
187
|
+
static inline void clear_subscriptions(ws_s *ws) {
|
188
|
+
subscription_s *s;
|
189
|
+
fio_list_for_each(subscription_s, node, s, ws->subscriptions) {
|
190
|
+
pubsub_unsubscribe(s->sub);
|
191
|
+
free_subscription(s);
|
192
|
+
}
|
193
|
+
}
|
194
|
+
|
159
195
|
/*******************************************************************************
|
160
196
|
The Websocket Protocol implementation
|
161
197
|
*/
|
@@ -168,7 +204,10 @@ static void ws_ping(intptr_t fd, protocol_s *ws) {
|
|
168
204
|
.dealloc = SOCK_DEALLOC_NOOP);
|
169
205
|
}
|
170
206
|
|
171
|
-
static void on_close(protocol_s *_ws) {
|
207
|
+
static void on_close(intptr_t uuid, protocol_s *_ws) {
|
208
|
+
destroy_ws((ws_s *)_ws);
|
209
|
+
(void)uuid;
|
210
|
+
}
|
172
211
|
|
173
212
|
static void on_ready(intptr_t fduuid, protocol_s *ws) {
|
174
213
|
(void)(fduuid);
|
@@ -194,6 +233,12 @@ static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
|
|
194
233
|
static size_t websocket_encode(void *buff, void *data, size_t len, char text,
|
195
234
|
char first, char last, char client);
|
196
235
|
|
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
|
+
|
197
242
|
/* read data from the socket, parse it and invoke the websocket events. */
|
198
243
|
static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
199
244
|
#define ws ((ws_s *)_ws)
|
@@ -201,8 +246,15 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
201
246
|
return;
|
202
247
|
ssize_t len = 0;
|
203
248
|
ssize_t data_len = 0;
|
249
|
+
if (ws->resume_from) {
|
250
|
+
sock_touch(sockfd);
|
251
|
+
len = ws->resume_from;
|
252
|
+
ws->resume_from = 0;
|
253
|
+
goto resume_parsing;
|
254
|
+
}
|
204
255
|
while ((len = sock_read(sockfd, read_buffer.buffer, WEBSOCKET_READ_MAX)) >
|
205
256
|
0) {
|
257
|
+
resume_parsing:
|
206
258
|
data_len = 0;
|
207
259
|
read_buffer.pos = 0;
|
208
260
|
while (read_buffer.pos < len) {
|
@@ -424,6 +476,13 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
|
|
424
476
|
*((char *)(&(ws->parser.head))) = 0;
|
425
477
|
ws->parser.received = ws->parser.length = ws->parser.psize.len2 =
|
426
478
|
data_len = 0;
|
479
|
+
if (read_buffer.pos < len) {
|
480
|
+
/* we just finished an on_message callback, let's fragment the event. */
|
481
|
+
ws->resume_from = len;
|
482
|
+
facil_defer(.task = on_data_def, .task_type = FIO_PR_LOCK_TASK,
|
483
|
+
.uuid = sockfd);
|
484
|
+
return;
|
485
|
+
}
|
427
486
|
}
|
428
487
|
}
|
429
488
|
#undef ws
|
@@ -436,19 +495,19 @@ Create/Destroy the websocket object
|
|
436
495
|
static ws_s *new_websocket() {
|
437
496
|
// allocate the protocol object
|
438
497
|
ws_s *ws = malloc(sizeof(*ws));
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
// return the object
|
498
|
+
*ws = (ws_s){
|
499
|
+
.protocol.service = WEBSOCKET_ID_STR,
|
500
|
+
.protocol.ping = ws_ping,
|
501
|
+
.protocol.on_data = on_data,
|
502
|
+
.protocol.on_close = on_close,
|
503
|
+
.protocol.on_ready = on_ready,
|
504
|
+
.protocol.on_shutdown = on_shutdown,
|
505
|
+
.subscriptions = FIO_LIST_INIT_STATIC(ws->subscriptions),
|
506
|
+
};
|
449
507
|
return ws;
|
450
508
|
}
|
451
509
|
static void destroy_ws(ws_s *ws) {
|
510
|
+
clear_subscriptions(ws);
|
452
511
|
if (ws->on_close)
|
453
512
|
ws->on_close(ws);
|
454
513
|
free_ws_buffer(ws, ws->buffer);
|
@@ -610,6 +669,235 @@ static void websocket_write_impl(intptr_t fd, void *data, size_t len,
|
|
610
669
|
return;
|
611
670
|
}
|
612
671
|
|
672
|
+
/* *****************************************************************************
|
673
|
+
UTF-8 testing. This part was practically copied from:
|
674
|
+
https://stackoverflow.com/a/22135005/4025095
|
675
|
+
and
|
676
|
+
http://bjoern.hoehrmann.de/utf-8/decoder/dfa
|
677
|
+
***************************************************************************** */
|
678
|
+
/* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> */
|
679
|
+
/* See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. */
|
680
|
+
|
681
|
+
#define UTF8_ACCEPT 0
|
682
|
+
#define UTF8_REJECT 1
|
683
|
+
|
684
|
+
static const uint8_t utf8d[] = {
|
685
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
686
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
687
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
|
688
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
689
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
690
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
|
691
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
692
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
693
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
|
694
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
695
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
696
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
|
697
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
698
|
+
1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9,
|
699
|
+
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
|
700
|
+
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
701
|
+
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
702
|
+
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
|
703
|
+
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
704
|
+
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
705
|
+
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
|
706
|
+
0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
|
707
|
+
0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
|
708
|
+
0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8,
|
709
|
+
0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
|
710
|
+
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4,
|
711
|
+
0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
712
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
713
|
+
1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
|
714
|
+
1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
715
|
+
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1,
|
716
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
717
|
+
1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
718
|
+
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,
|
719
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
720
|
+
1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
721
|
+
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1,
|
722
|
+
1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1,
|
723
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
|
724
|
+
};
|
725
|
+
|
726
|
+
static inline uint32_t validate_utf8(uint8_t *str, size_t len) {
|
727
|
+
uint32_t state = 0;
|
728
|
+
uint32_t type;
|
729
|
+
while (len) {
|
730
|
+
type = utf8d[*str];
|
731
|
+
state = utf8d[256 + state * 16 + type];
|
732
|
+
if (state == UTF8_REJECT)
|
733
|
+
return 0;
|
734
|
+
len--;
|
735
|
+
str++;
|
736
|
+
}
|
737
|
+
return state == 0;
|
738
|
+
}
|
739
|
+
/* *****************************************************************************
|
740
|
+
Subscription handling
|
741
|
+
***************************************************************************** */
|
742
|
+
|
743
|
+
typedef struct {
|
744
|
+
void (*on_message)(websocket_pubsub_notification_s notification);
|
745
|
+
void (*on_unsubscribe)(void *udata);
|
746
|
+
void *udata;
|
747
|
+
} websocket_sub_data_s;
|
748
|
+
|
749
|
+
static void websocket_on_unsubscribe(void *u1, void *u2) {
|
750
|
+
websocket_sub_data_s *d = u2;
|
751
|
+
(void)u1;
|
752
|
+
if (d->on_unsubscribe)
|
753
|
+
d->on_unsubscribe(d->udata);
|
754
|
+
free(d);
|
755
|
+
}
|
756
|
+
|
757
|
+
static void websocket_on_pubsub_message_direct(pubsub_message_s *msg) {
|
758
|
+
protocol_s *pr =
|
759
|
+
facil_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE);
|
760
|
+
if (!pr) {
|
761
|
+
if (errno == EBADF)
|
762
|
+
return;
|
763
|
+
pubsub_defer(msg);
|
764
|
+
return;
|
765
|
+
}
|
766
|
+
websocket_write((ws_s *)pr, msg->msg.data, msg->msg.len,
|
767
|
+
msg->msg.len >= (2 << 14)
|
768
|
+
? 0
|
769
|
+
: validate_utf8((uint8_t *)msg->msg.data, msg->msg.len));
|
770
|
+
facil_protocol_unlock(pr, FIO_PR_LOCK_WRITE);
|
771
|
+
}
|
772
|
+
|
773
|
+
static void websocket_on_pubsub_message_direct_txt(pubsub_message_s *msg) {
|
774
|
+
protocol_s *pr =
|
775
|
+
facil_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE);
|
776
|
+
if (!pr) {
|
777
|
+
if (errno == EBADF)
|
778
|
+
return;
|
779
|
+
pubsub_defer(msg);
|
780
|
+
return;
|
781
|
+
}
|
782
|
+
websocket_write((ws_s *)pr, msg->msg.data, msg->msg.len, 1);
|
783
|
+
facil_protocol_unlock(pr, FIO_PR_LOCK_WRITE);
|
784
|
+
}
|
785
|
+
|
786
|
+
static void websocket_on_pubsub_message_direct_bin(pubsub_message_s *msg) {
|
787
|
+
protocol_s *pr =
|
788
|
+
facil_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE);
|
789
|
+
if (!pr) {
|
790
|
+
if (errno == EBADF)
|
791
|
+
return;
|
792
|
+
pubsub_defer(msg);
|
793
|
+
return;
|
794
|
+
}
|
795
|
+
websocket_write((ws_s *)pr, msg->msg.data, msg->msg.len, 0);
|
796
|
+
facil_protocol_unlock(pr, FIO_PR_LOCK_WRITE);
|
797
|
+
}
|
798
|
+
|
799
|
+
static void websocket_on_pubsub_message(pubsub_message_s *msg) {
|
800
|
+
protocol_s *pr =
|
801
|
+
facil_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_TASK);
|
802
|
+
if (!pr) {
|
803
|
+
if (errno == EBADF)
|
804
|
+
return;
|
805
|
+
pubsub_defer(msg);
|
806
|
+
return;
|
807
|
+
}
|
808
|
+
websocket_sub_data_s *d = msg->udata2;
|
809
|
+
|
810
|
+
if (d->on_message)
|
811
|
+
d->on_message((websocket_pubsub_notification_s){
|
812
|
+
.ws = (ws_s *)pr,
|
813
|
+
.engine = (pubsub_engine_s *)msg->engine,
|
814
|
+
.subscription_id = (intptr_t)msg->subscription,
|
815
|
+
.channel = {.name = msg->channel.name, .len = msg->channel.len},
|
816
|
+
.msg = {.data = msg->msg.data, .len = msg->msg.len},
|
817
|
+
.use_pattern = msg->use_pattern,
|
818
|
+
});
|
819
|
+
facil_protocol_unlock(pr, FIO_PR_LOCK_TASK);
|
820
|
+
}
|
821
|
+
|
822
|
+
/**
|
823
|
+
* Returns a subscription ID on success and 0 on failure.
|
824
|
+
*/
|
825
|
+
#undef websocket_subscribe
|
826
|
+
uintptr_t websocket_subscribe(struct websocket_subscribe_s args) {
|
827
|
+
websocket_sub_data_s *d = malloc(sizeof(*d));
|
828
|
+
*d = (websocket_sub_data_s){.udata = args.udata,
|
829
|
+
.on_message = args.on_message,
|
830
|
+
.on_unsubscribe = args.on_unsubscribe};
|
831
|
+
pubsub_sub_pt sub = pubsub_subscribe(
|
832
|
+
.engine = args.engine,
|
833
|
+
.channel =
|
834
|
+
{
|
835
|
+
.name = (char *)args.channel.name, .len = args.channel.len,
|
836
|
+
},
|
837
|
+
.use_pattern = args.use_pattern,
|
838
|
+
.on_unsubscribe = websocket_on_unsubscribe,
|
839
|
+
.on_message =
|
840
|
+
(args.on_message
|
841
|
+
? websocket_on_pubsub_message
|
842
|
+
: args.force_binary
|
843
|
+
? websocket_on_pubsub_message_direct_bin
|
844
|
+
: args.force_text
|
845
|
+
? websocket_on_pubsub_message_direct_txt
|
846
|
+
: websocket_on_pubsub_message_direct),
|
847
|
+
.udata1 = (void *)args.ws->fd, .udata2 = d);
|
848
|
+
if (!sub) {
|
849
|
+
free(d);
|
850
|
+
return 0;
|
851
|
+
}
|
852
|
+
subscription_s *s = create_subscription(args.ws, sub);
|
853
|
+
return (uintptr_t)s;
|
854
|
+
}
|
855
|
+
|
856
|
+
/**
|
857
|
+
* Returns the existing subscription's ID (if exists) or 0 (no subscription).
|
858
|
+
*/
|
859
|
+
#undef websocket_find_sub
|
860
|
+
uintptr_t websocket_find_sub(struct websocket_subscribe_s args) {
|
861
|
+
pubsub_sub_pt sub = pubsub_find_sub(
|
862
|
+
.engine = args.engine,
|
863
|
+
.channel =
|
864
|
+
{
|
865
|
+
.name = (char *)args.channel.name, .len = args.channel.len,
|
866
|
+
},
|
867
|
+
.use_pattern = args.use_pattern,
|
868
|
+
.on_message =
|
869
|
+
(args.on_message
|
870
|
+
? websocket_on_pubsub_message
|
871
|
+
: args.force_binary
|
872
|
+
? websocket_on_pubsub_message_direct_bin
|
873
|
+
: args.force_text
|
874
|
+
? websocket_on_pubsub_message_direct_txt
|
875
|
+
: websocket_on_pubsub_message_direct),
|
876
|
+
.udata1 = (void *)args.ws->fd, .udata2 = args.udata);
|
877
|
+
if (!sub)
|
878
|
+
return 0;
|
879
|
+
subscription_s *s;
|
880
|
+
fio_list_for_each(subscription_s, node, s, args.ws->subscriptions) {
|
881
|
+
if (s->sub == sub)
|
882
|
+
return (uintptr_t)s;
|
883
|
+
}
|
884
|
+
return 0;
|
885
|
+
}
|
886
|
+
|
887
|
+
/**
|
888
|
+
* Unsubscribes from a channel.
|
889
|
+
*/
|
890
|
+
void websocket_unsubscribe(ws_s *ws, uintptr_t subscription_id) {
|
891
|
+
subscription_s *s;
|
892
|
+
fio_list_for_each(subscription_s, node, s, ws->subscriptions) {
|
893
|
+
if (s == (subscription_s *)subscription_id) {
|
894
|
+
pubsub_unsubscribe(s->sub);
|
895
|
+
free_subscription(s);
|
896
|
+
return;
|
897
|
+
}
|
898
|
+
}
|
899
|
+
}
|
900
|
+
|
613
901
|
/*******************************************************************************
|
614
902
|
The API implementation
|
615
903
|
*/
|