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
@@ -0,0 +1,277 @@
|
|
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
|
+
#include "spnlock.inc"
|
9
|
+
|
10
|
+
#include "fio_list.h"
|
11
|
+
#include "redis_connection.h"
|
12
|
+
#include <errno.h>
|
13
|
+
#include <math.h>
|
14
|
+
#include <signal.h>
|
15
|
+
#include <string.h>
|
16
|
+
#include <strings.h>
|
17
|
+
|
18
|
+
/* *****************************************************************************
|
19
|
+
Memory and Data structures - The protocol object.
|
20
|
+
***************************************************************************** */
|
21
|
+
#ifndef REDIS_POOL_SIZES /* per protocol */
|
22
|
+
#define REDIS_POOL_SIZES 256
|
23
|
+
#endif
|
24
|
+
|
25
|
+
#ifndef REDIS_READ_BUFFER /* during network data events */
|
26
|
+
#define REDIS_READ_BUFFER 2048
|
27
|
+
#endif
|
28
|
+
|
29
|
+
/* a protocol ID string */
|
30
|
+
static const char *REDIS_PROTOCOL_ID =
|
31
|
+
"Redis Protocol for the facil.io framework";
|
32
|
+
/* Pings */
|
33
|
+
#define REDIS_PING_LEN 24
|
34
|
+
static const char REDIS_PING_STR[] = "*2\r\n"
|
35
|
+
"$4\r\nPING\r\n"
|
36
|
+
"$24\r\nfacil.io connection PING\r\n";
|
37
|
+
static const char REDIS_PING_PAYLOAD[] = "facil.io connection PING";
|
38
|
+
|
39
|
+
typedef struct {
|
40
|
+
protocol_s protocol;
|
41
|
+
struct redis_context_args *settings;
|
42
|
+
unsigned authenticated : 1;
|
43
|
+
} redis_protocol_s;
|
44
|
+
|
45
|
+
static inline size_t ul2a(char *dest, size_t num) {
|
46
|
+
uint8_t digits = 1;
|
47
|
+
size_t tmp = num;
|
48
|
+
while ((tmp /= 10))
|
49
|
+
++digits;
|
50
|
+
|
51
|
+
if (dest) {
|
52
|
+
dest += digits;
|
53
|
+
*(dest--) = 0;
|
54
|
+
}
|
55
|
+
for (size_t i = 0; i < digits; i++) {
|
56
|
+
num = num - (10 * (tmp = (num / 10)));
|
57
|
+
if (dest)
|
58
|
+
*(dest--) = '0' + num;
|
59
|
+
num = tmp;
|
60
|
+
}
|
61
|
+
return digits;
|
62
|
+
}
|
63
|
+
|
64
|
+
/* *****************************************************************************
|
65
|
+
The Protocol Context
|
66
|
+
***************************************************************************** */
|
67
|
+
#undef redis_create_context
|
68
|
+
void *redis_create_context(struct redis_context_args args) {
|
69
|
+
if (!args.on_message || !args.parser) {
|
70
|
+
fprintf(stderr, "A Redix connection context requires both an `on_message` "
|
71
|
+
"callback and a parser.\n");
|
72
|
+
exit(EINVAL);
|
73
|
+
}
|
74
|
+
if (args.auth) {
|
75
|
+
if (!args.auth_len)
|
76
|
+
args.auth_len = strlen(args.auth);
|
77
|
+
args.auth_len++;
|
78
|
+
}
|
79
|
+
struct redis_context_args *c = malloc(sizeof(*c) + args.auth_len);
|
80
|
+
*c = args;
|
81
|
+
if (args.auth) {
|
82
|
+
c->auth_len--;
|
83
|
+
c->auth = (char *)(c + 1);
|
84
|
+
memcpy(c->auth, args.auth, c->auth_len);
|
85
|
+
c->auth[c->auth_len] = 0;
|
86
|
+
}
|
87
|
+
return c;
|
88
|
+
}
|
89
|
+
|
90
|
+
/* *****************************************************************************
|
91
|
+
The Protocol Callbacks
|
92
|
+
***************************************************************************** */
|
93
|
+
|
94
|
+
void redis_protocol_cleanup(intptr_t uuid, void *settings) {
|
95
|
+
struct redis_context_args *s = settings;
|
96
|
+
if (s->on_close)
|
97
|
+
s->on_close(uuid, s->udata);
|
98
|
+
if (s->autodestruct)
|
99
|
+
free(settings);
|
100
|
+
}
|
101
|
+
|
102
|
+
static void redis_on_close_client(intptr_t uuid, protocol_s *pr) {
|
103
|
+
redis_protocol_s *r = (redis_protocol_s *)pr;
|
104
|
+
// if (r->settings->on_close)
|
105
|
+
// r->settings->on_close(uuid, r->settings->udata);
|
106
|
+
redis_protocol_cleanup(uuid, r->settings);
|
107
|
+
free(r);
|
108
|
+
}
|
109
|
+
|
110
|
+
static void redis_on_close_server(intptr_t uuid, protocol_s *pr) {
|
111
|
+
redis_protocol_s *r = (redis_protocol_s *)pr;
|
112
|
+
if (r->settings->on_close)
|
113
|
+
r->settings->on_close(uuid, r->settings->udata);
|
114
|
+
free(r);
|
115
|
+
}
|
116
|
+
|
117
|
+
/** called when a connection's timeout was reached */
|
118
|
+
static void redis_ping(intptr_t uuid, protocol_s *pr) {
|
119
|
+
/* We cannow write directly to the socket in case `redis_send` has scheduled
|
120
|
+
* callbacks. */
|
121
|
+
sock_write2(.uuid = uuid, .buffer = REDIS_PING_STR,
|
122
|
+
.length = sizeof(REDIS_PING_STR) - 1, .move = 1,
|
123
|
+
.dealloc = SOCK_DEALLOC_NOOP);
|
124
|
+
(void)pr;
|
125
|
+
}
|
126
|
+
|
127
|
+
/** called when a data is available, but will not run concurrently */
|
128
|
+
static void redis_on_data_deferred(intptr_t uuid, protocol_s *pr, void *d);
|
129
|
+
static void redis_on_data(intptr_t uuid, protocol_s *pr) {
|
130
|
+
redis_protocol_s *r = (redis_protocol_s *)pr;
|
131
|
+
uint8_t buffer[REDIS_READ_BUFFER];
|
132
|
+
ssize_t len, limit, pos;
|
133
|
+
resp_object_s *msg;
|
134
|
+
limit = len = sock_read(uuid, buffer, REDIS_READ_BUFFER);
|
135
|
+
if (len <= 0)
|
136
|
+
return;
|
137
|
+
pos = 0;
|
138
|
+
while (len) {
|
139
|
+
msg = resp_parser_feed(r->settings->parser, buffer + pos, (size_t *)&len);
|
140
|
+
if (!len && !msg) {
|
141
|
+
fprintf(stderr, "ERROR: (RESP Parser) Bad input (%lu):\n%s\n", limit,
|
142
|
+
buffer);
|
143
|
+
/* we'll simply ignore bad input. skip a line. */
|
144
|
+
for (; pos < limit; pos++) {
|
145
|
+
len++;
|
146
|
+
if (buffer[pos] == '\n') {
|
147
|
+
pos++;
|
148
|
+
len++;
|
149
|
+
break; /* from `for` loop */
|
150
|
+
}
|
151
|
+
}
|
152
|
+
continue; /* while loop */
|
153
|
+
}
|
154
|
+
if (msg) {
|
155
|
+
if (!r->authenticated) {
|
156
|
+
r->authenticated = 1;
|
157
|
+
if (msg->type != RESP_OK) {
|
158
|
+
if (msg->type == RESP_ERR) {
|
159
|
+
fprintf(stderr,
|
160
|
+
"ERROR: (RedisConnection) Authentication FAILED.\n"
|
161
|
+
" %s\n",
|
162
|
+
resp_obj2str(msg)->string);
|
163
|
+
} else {
|
164
|
+
fprintf(stderr,
|
165
|
+
"ERROR: (RedisConnection) Authentication FAILED "
|
166
|
+
"(unexpected response %d).\n",
|
167
|
+
msg->type);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
} else if ((msg->type == RESP_STRING &&
|
171
|
+
resp_obj2str(msg)->len == REDIS_PING_LEN &&
|
172
|
+
!memcmp(resp_obj2str(msg)->string, REDIS_PING_PAYLOAD,
|
173
|
+
REDIS_PING_LEN)) ||
|
174
|
+
(msg->type == RESP_ARRAY &&
|
175
|
+
resp_obj2arr(msg)->array[0]->type == RESP_STRING &&
|
176
|
+
resp_obj2str(resp_obj2arr(msg)->array[0])->len == 4 &&
|
177
|
+
resp_obj2arr(msg)->array[1]->type == RESP_STRING &&
|
178
|
+
resp_obj2str(resp_obj2arr(msg)->array[1])->len ==
|
179
|
+
REDIS_PING_LEN &&
|
180
|
+
!strncasecmp(
|
181
|
+
(char *)resp_obj2str(resp_obj2arr(msg)->array[0])->string,
|
182
|
+
"pong", 4) &&
|
183
|
+
!memcmp(
|
184
|
+
(char *)resp_obj2str(resp_obj2arr(msg)->array[0])->string,
|
185
|
+
REDIS_PING_PAYLOAD, REDIS_PING_LEN))) {
|
186
|
+
/* an internal ping, do not forward. */
|
187
|
+
} else
|
188
|
+
r->settings->on_message(uuid, msg, r->settings->udata);
|
189
|
+
resp_free_object(msg);
|
190
|
+
msg = NULL;
|
191
|
+
}
|
192
|
+
if (len == limit) {
|
193
|
+
/* fragment events, it's edge polling, so we need to read everything */
|
194
|
+
facil_defer(.uuid = uuid, .task = redis_on_data_deferred,
|
195
|
+
.task_type = FIO_PR_LOCK_TASK);
|
196
|
+
return;
|
197
|
+
}
|
198
|
+
pos += len;
|
199
|
+
limit = len = limit - len;
|
200
|
+
}
|
201
|
+
}
|
202
|
+
static void redis_on_data_deferred(intptr_t uuid, protocol_s *pr, void *d) {
|
203
|
+
if (!pr || pr->service != REDIS_PROTOCOL_ID)
|
204
|
+
return;
|
205
|
+
redis_on_data(uuid, pr);
|
206
|
+
(void)d;
|
207
|
+
}
|
208
|
+
|
209
|
+
static void redis_on_open(intptr_t uuid, protocol_s *pr, void *d) {
|
210
|
+
redis_protocol_s *r = (void *)pr;
|
211
|
+
facil_set_timeout(uuid, r->settings->ping);
|
212
|
+
if (r->settings->auth) {
|
213
|
+
size_t n_len = ul2a(NULL, r->settings->auth_len);
|
214
|
+
char *t =
|
215
|
+
malloc(r->settings->auth_len + 20 + n_len); /* 19 is probably enough */
|
216
|
+
memcpy(t, "*2\r\n$4\r\nAUTH\r\n$", 15);
|
217
|
+
ul2a(t + 15, r->settings->auth_len);
|
218
|
+
t[15 + n_len] = '\r';
|
219
|
+
t[16 + n_len] = '\n';
|
220
|
+
memcpy(t + 17 + n_len, r->settings->auth, r->settings->auth_len);
|
221
|
+
t[17 + n_len + r->settings->auth_len] = '\r';
|
222
|
+
t[18 + n_len + r->settings->auth_len] = '\n';
|
223
|
+
t[19 + n_len + r->settings->auth_len] = 0; /* we don't need it, but nice */
|
224
|
+
sock_write2(.uuid = uuid, .buffer = t,
|
225
|
+
.length = (19 + n_len + r->settings->auth_len), .move = 1);
|
226
|
+
|
227
|
+
} else
|
228
|
+
r->authenticated = 1;
|
229
|
+
r->settings->on_open(uuid, r->settings->udata);
|
230
|
+
(void)d;
|
231
|
+
}
|
232
|
+
|
233
|
+
/**
|
234
|
+
* This function is used as a function pointer for the `facil_connect` and
|
235
|
+
* calls (the `on_connect` callback).
|
236
|
+
*/
|
237
|
+
protocol_s *redis_create_client_protocol(intptr_t uuid, void *settings) {
|
238
|
+
redis_protocol_s *r = malloc(sizeof(*r));
|
239
|
+
*r = (redis_protocol_s){
|
240
|
+
.protocol =
|
241
|
+
{
|
242
|
+
.service = REDIS_PROTOCOL_ID,
|
243
|
+
.on_data = redis_on_data,
|
244
|
+
.on_close = redis_on_close_client,
|
245
|
+
.ping = redis_ping,
|
246
|
+
},
|
247
|
+
.settings = settings,
|
248
|
+
};
|
249
|
+
facil_set_timeout(uuid, r->settings->ping);
|
250
|
+
if (r->settings->on_open)
|
251
|
+
facil_defer(.task = redis_on_open, .uuid = uuid);
|
252
|
+
return &r->protocol;
|
253
|
+
(void)uuid;
|
254
|
+
}
|
255
|
+
|
256
|
+
/**
|
257
|
+
* This function is used as a function pointer for the `facil_listen` calls (the
|
258
|
+
* `on_open` callbacks).
|
259
|
+
*/
|
260
|
+
protocol_s *redis_create_server_protocol(intptr_t uuid, void *settings) {
|
261
|
+
redis_protocol_s *r = malloc(sizeof(*r));
|
262
|
+
*r = (redis_protocol_s){
|
263
|
+
.protocol =
|
264
|
+
{
|
265
|
+
.service = REDIS_PROTOCOL_ID,
|
266
|
+
.on_data = redis_on_data,
|
267
|
+
.on_close = redis_on_close_server,
|
268
|
+
.ping = redis_ping,
|
269
|
+
},
|
270
|
+
.settings = settings,
|
271
|
+
};
|
272
|
+
facil_set_timeout(uuid, r->settings->ping);
|
273
|
+
if (r->settings->on_open)
|
274
|
+
facil_defer(.task = redis_on_open, .uuid = uuid);
|
275
|
+
return &r->protocol;
|
276
|
+
(void)uuid;
|
277
|
+
}
|
@@ -0,0 +1,77 @@
|
|
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_REDIS_CONNECTION_H
|
9
|
+
#define H_REDIS_CONNECTION_H
|
10
|
+
#include "facil.h"
|
11
|
+
#include "resp.h"
|
12
|
+
|
13
|
+
/* *****************************************************************************
|
14
|
+
Connectivity
|
15
|
+
***************************************************************************** */
|
16
|
+
|
17
|
+
/**
|
18
|
+
To create a new Redis connection, create a Redis context object using the
|
19
|
+
following optings and the `redis_create_context` function.
|
20
|
+
|
21
|
+
The context should be passed along as the `udata` parameter to either
|
22
|
+
`facil_connect` or `facil_listen`.
|
23
|
+
|
24
|
+
The `on_connect` / `on_open` argument for `facil_connect` and
|
25
|
+
`facil_listen`should be the `redis_create_protocol` function pointer.
|
26
|
+
|
27
|
+
The `on_fail` / `on_finish` argument for `facil_connect` and
|
28
|
+
`facil_listen`should be the `redis_protocol_cleanup` function pointer.
|
29
|
+
*/
|
30
|
+
|
31
|
+
struct redis_context_args {
|
32
|
+
/** REQUIRED: the RESP parser used by the connection. */
|
33
|
+
resp_parser_pt parser;
|
34
|
+
/** REQUIRED: called when the RESP messages are received. */
|
35
|
+
void (*on_message)(intptr_t uuid, resp_object_s *msg, void *udata);
|
36
|
+
/** called when the Redix connection closes or fails to open. */
|
37
|
+
void (*on_close)(intptr_t uuid, void *udata);
|
38
|
+
/** called when the Redix connection opens. */
|
39
|
+
void (*on_open)(intptr_t uuid, void *udata);
|
40
|
+
/** Authentication string (password). */
|
41
|
+
char *auth;
|
42
|
+
/** Authentication string (password) length. */
|
43
|
+
size_t auth_len;
|
44
|
+
/** Opaque user data. */
|
45
|
+
void *udata;
|
46
|
+
/** PING intervals. */
|
47
|
+
uint8_t ping;
|
48
|
+
/** The context, not the computer... */
|
49
|
+
uint8_t autodestruct;
|
50
|
+
};
|
51
|
+
|
52
|
+
void *redis_create_context(struct redis_context_args);
|
53
|
+
|
54
|
+
#define redis_create_context(...) \
|
55
|
+
redis_create_context((struct redis_context_args){__VA_ARGS__})
|
56
|
+
|
57
|
+
/**
|
58
|
+
* This function is used as a function pointer for the `facil_connect` and
|
59
|
+
* calls (the `on_connect` callback).
|
60
|
+
*/
|
61
|
+
protocol_s *redis_create_client_protocol(intptr_t uuid, void *settings);
|
62
|
+
|
63
|
+
/**
|
64
|
+
* This function is used as a function pointer for the `facil_listen` calls (the
|
65
|
+
* `on_open` callbacks).
|
66
|
+
*/
|
67
|
+
protocol_s *redis_create_server_protocol(intptr_t uuid, void *settings);
|
68
|
+
|
69
|
+
/**
|
70
|
+
* This function is used as a function pointer for both `facil_connect` and
|
71
|
+
* `facil_listen` calls (the `on_fail` / `on_finish` callbacks).
|
72
|
+
*
|
73
|
+
* It's main responsibility is to free the context and protocol resources.
|
74
|
+
*/
|
75
|
+
void redis_protocol_cleanup(intptr_t uuid, void *settings);
|
76
|
+
|
77
|
+
#endif
|
@@ -0,0 +1,398 @@
|
|
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
|
+
#include "spnlock.inc"
|
9
|
+
|
10
|
+
#include "fio_list.h"
|
11
|
+
#include "redis_connection.h"
|
12
|
+
#include "redis_engine.h"
|
13
|
+
#include "resp.h"
|
14
|
+
|
15
|
+
#include <string.h>
|
16
|
+
/* *****************************************************************************
|
17
|
+
Data Structures / State
|
18
|
+
***************************************************************************** */
|
19
|
+
|
20
|
+
typedef struct {
|
21
|
+
pubsub_engine_s engine;
|
22
|
+
resp_parser_pt sub_parser;
|
23
|
+
resp_parser_pt pub_parser;
|
24
|
+
intptr_t sub;
|
25
|
+
intptr_t pub;
|
26
|
+
void *sub_ctx;
|
27
|
+
void *pub_ctx;
|
28
|
+
char *address;
|
29
|
+
char *port;
|
30
|
+
fio_list_s callbacks;
|
31
|
+
uint16_t ref;
|
32
|
+
volatile uint8_t active;
|
33
|
+
volatile uint8_t sub_state;
|
34
|
+
volatile uint8_t pub_state;
|
35
|
+
spn_lock_i lock;
|
36
|
+
} redis_engine_s;
|
37
|
+
|
38
|
+
typedef struct {
|
39
|
+
fio_list_s node;
|
40
|
+
void (*callback)(pubsub_engine_s *e, resp_object_s *reply, void *udata);
|
41
|
+
void *udata;
|
42
|
+
size_t len;
|
43
|
+
uint8_t sent;
|
44
|
+
} callbacks_s;
|
45
|
+
|
46
|
+
static int dealloc_engine(redis_engine_s *r) {
|
47
|
+
if (!spn_sub(&r->ref, 1)) {
|
48
|
+
resp_parser_destroy(r->sub_parser);
|
49
|
+
resp_parser_destroy(r->pub_parser);
|
50
|
+
free(r);
|
51
|
+
return -1;
|
52
|
+
}
|
53
|
+
return 0;
|
54
|
+
}
|
55
|
+
|
56
|
+
/* *****************************************************************************
|
57
|
+
Writing commands
|
58
|
+
***************************************************************************** */
|
59
|
+
static void redis_pub_send(void *e, void *uuid) {
|
60
|
+
redis_engine_s *r = e;
|
61
|
+
callbacks_s *cb;
|
62
|
+
spn_lock(&r->lock);
|
63
|
+
|
64
|
+
if (fio_list_any(r->callbacks)) {
|
65
|
+
cb = fio_node2obj(callbacks_s, node, r->callbacks.next);
|
66
|
+
if (cb->sent == 0) {
|
67
|
+
cb->sent = 1;
|
68
|
+
sock_write2(.uuid = r->pub, .buffer = (uint8_t *)(cb + 1),
|
69
|
+
.length = cb->len, .move = 1, .dealloc = SOCK_DEALLOC_NOOP);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
spn_unlock(&r->lock);
|
73
|
+
dealloc_engine(r);
|
74
|
+
(void)uuid;
|
75
|
+
}
|
76
|
+
|
77
|
+
static void schedule_pub_send(redis_engine_s *r, intptr_t uuid) {
|
78
|
+
spn_add(&r->ref, 1);
|
79
|
+
defer(redis_pub_send, r, (void *)uuid);
|
80
|
+
}
|
81
|
+
|
82
|
+
/* *****************************************************************************
|
83
|
+
Engine Bridge
|
84
|
+
***************************************************************************** */
|
85
|
+
|
86
|
+
static void on_message_sub(intptr_t uuid, resp_object_s *msg, void *udata) {
|
87
|
+
if (msg->type == RESP_PUBSUB) {
|
88
|
+
pubsub_engine_distribute(
|
89
|
+
.engine = udata,
|
90
|
+
.channel =
|
91
|
+
{.name =
|
92
|
+
(char *)resp_obj2str(resp_obj2arr(msg)->array[1])->string,
|
93
|
+
.len = resp_obj2str(resp_obj2arr(msg)->array[1])->len},
|
94
|
+
.msg = {
|
95
|
+
.data =
|
96
|
+
(char *)resp_obj2str(resp_obj2arr(msg)->array[2])->string,
|
97
|
+
.len = resp_obj2str(resp_obj2arr(msg)->array[2])->len});
|
98
|
+
return;
|
99
|
+
}
|
100
|
+
(void)uuid;
|
101
|
+
}
|
102
|
+
|
103
|
+
static void on_message_pub(intptr_t uuid, resp_object_s *msg, void *udata) {
|
104
|
+
redis_engine_s *r = udata;
|
105
|
+
callbacks_s *cb;
|
106
|
+
spn_lock(&r->lock);
|
107
|
+
cb = fio_list_shift(callbacks_s, node, r->callbacks);
|
108
|
+
spn_unlock(&r->lock);
|
109
|
+
if (cb) {
|
110
|
+
schedule_pub_send(r, uuid);
|
111
|
+
if (cb->callback)
|
112
|
+
cb->callback(&r->engine, msg, cb->udata);
|
113
|
+
free(cb);
|
114
|
+
} else {
|
115
|
+
uint8_t buffer[64] = {0};
|
116
|
+
size_t len = 63;
|
117
|
+
resp_format(NULL, buffer, &len, msg);
|
118
|
+
fprintf(stderr,
|
119
|
+
"WARN: (RedisEngine) Possible issue, "
|
120
|
+
"received unknown message (%lu bytes):\n%s\n",
|
121
|
+
len, (char *)buffer);
|
122
|
+
}
|
123
|
+
(void)uuid;
|
124
|
+
}
|
125
|
+
|
126
|
+
/* *****************************************************************************
|
127
|
+
Connections
|
128
|
+
***************************************************************************** */
|
129
|
+
|
130
|
+
static inline int connect_sub(redis_engine_s *r) {
|
131
|
+
spn_add(&r->ref, 1);
|
132
|
+
return (r->sub = facil_connect(.address = r->address, .port = r->port,
|
133
|
+
.on_connect = redis_create_client_protocol,
|
134
|
+
.udata = r->sub_ctx,
|
135
|
+
.on_fail = redis_protocol_cleanup));
|
136
|
+
}
|
137
|
+
|
138
|
+
static inline int connect_pub(redis_engine_s *r) {
|
139
|
+
spn_add(&r->ref, 1);
|
140
|
+
return (r->pub = facil_connect(.address = r->address, .port = r->port,
|
141
|
+
.on_connect = redis_create_client_protocol,
|
142
|
+
.udata = r->pub_ctx,
|
143
|
+
.on_fail = redis_protocol_cleanup));
|
144
|
+
}
|
145
|
+
|
146
|
+
static void on_close_sub(intptr_t uuid, void *p) {
|
147
|
+
redis_engine_s *r = p;
|
148
|
+
if (!defer_fork_is_active()) {
|
149
|
+
dealloc_engine(r);
|
150
|
+
return;
|
151
|
+
}
|
152
|
+
if (r->sub == uuid && r->active) {
|
153
|
+
if (r->sub_state) {
|
154
|
+
r->sub_state = 0;
|
155
|
+
fprintf(stderr,
|
156
|
+
"ERROR: (RedisEngine) redis Sub "
|
157
|
+
"connection LOST: %s:%s\n",
|
158
|
+
r->address ? r->address : "0.0.0.0", r->port);
|
159
|
+
}
|
160
|
+
connect_sub(r);
|
161
|
+
facil_run_every(50, 1, (void (*)(void *))pubsub_engine_resubscribe,
|
162
|
+
(void *)&r->engine, NULL);
|
163
|
+
}
|
164
|
+
dealloc_engine(r);
|
165
|
+
}
|
166
|
+
|
167
|
+
static void on_close_pub(intptr_t uuid, void *p) {
|
168
|
+
redis_engine_s *r = p;
|
169
|
+
if (!defer_fork_is_active()) {
|
170
|
+
dealloc_engine(r);
|
171
|
+
return;
|
172
|
+
}
|
173
|
+
if (r->pub == uuid && r->active) {
|
174
|
+
connect_pub(r);
|
175
|
+
if (r->pub_state) {
|
176
|
+
r->pub_state = 0;
|
177
|
+
fprintf(stderr,
|
178
|
+
"ERROR: (RedisEngine) redis Pub "
|
179
|
+
"connection LOST: %s:%s\n",
|
180
|
+
r->address ? r->address : "0.0.0.0", r->port);
|
181
|
+
}
|
182
|
+
}
|
183
|
+
dealloc_engine(r);
|
184
|
+
}
|
185
|
+
|
186
|
+
static void on_open_pub(intptr_t uuid, void *e) {
|
187
|
+
redis_engine_s *r = e;
|
188
|
+
if (r->pub != uuid)
|
189
|
+
return;
|
190
|
+
if (!r->pub_state) /* no message on first connection */
|
191
|
+
fprintf(stderr,
|
192
|
+
"INFO: (RedisEngine) redis Pub "
|
193
|
+
"connection (re)established: %s:%s\n",
|
194
|
+
r->address ? r->address : "0.0.0.0", r->port);
|
195
|
+
r->pub_state = 1;
|
196
|
+
spn_lock(&r->lock);
|
197
|
+
callbacks_s *cb;
|
198
|
+
fio_list_for_each(callbacks_s, node, cb, r->callbacks) { cb->sent = 0; }
|
199
|
+
spn_unlock(&r->lock);
|
200
|
+
schedule_pub_send(r, uuid);
|
201
|
+
}
|
202
|
+
|
203
|
+
static void on_open_sub(intptr_t uuid, void *e) {
|
204
|
+
redis_engine_s *r = e;
|
205
|
+
if (r->sub != uuid)
|
206
|
+
return;
|
207
|
+
if (!r->sub_state) /* no message on first connection */
|
208
|
+
fprintf(stderr,
|
209
|
+
"INFO: (RedisEngine) redis Sub "
|
210
|
+
"connection (re)established: %s:%s\n",
|
211
|
+
r->address ? r->address : "0.0.0.0", r->port);
|
212
|
+
r->sub_state = 1;
|
213
|
+
pubsub_engine_resubscribe(&r->engine);
|
214
|
+
(void)uuid;
|
215
|
+
}
|
216
|
+
|
217
|
+
/* *****************************************************************************
|
218
|
+
Callbacks
|
219
|
+
***************************************************************************** */
|
220
|
+
|
221
|
+
/** Should return 0 on success and -1 on failure. */
|
222
|
+
static int subscribe(const pubsub_engine_s *eng, const char *ch, size_t ch_len,
|
223
|
+
uint8_t use_pattern) {
|
224
|
+
redis_engine_s *e = (redis_engine_s *)eng;
|
225
|
+
if (!sock_isvalid(e->sub)) {
|
226
|
+
return 0;
|
227
|
+
}
|
228
|
+
resp_object_s *cmd = resp_arr2obj(2, NULL);
|
229
|
+
if (!cmd) {
|
230
|
+
return -1;
|
231
|
+
}
|
232
|
+
resp_obj2arr(cmd)->array[0] = use_pattern ? resp_str2obj("PSUBSCRIBE", 10)
|
233
|
+
: resp_str2obj("SUBSCRIBE", 9);
|
234
|
+
resp_obj2arr(cmd)->array[1] =
|
235
|
+
(ch ? resp_str2obj(ch, ch_len) : resp_nil2obj());
|
236
|
+
void *buffer = malloc(32 + ch_len);
|
237
|
+
size_t size = 32 + ch_len;
|
238
|
+
if (resp_format(e->sub_parser, buffer, &size, cmd))
|
239
|
+
fprintf(stderr, "ERROR: RESP format? size = %lu ch = %lu\n", size, ch_len);
|
240
|
+
sock_write2(.uuid = e->sub, .buffer = buffer, .length = size, .move = 1);
|
241
|
+
resp_free_object(cmd);
|
242
|
+
return 0;
|
243
|
+
}
|
244
|
+
|
245
|
+
/** Return value is ignored. */
|
246
|
+
static void unsubscribe(const pubsub_engine_s *eng, const char *ch,
|
247
|
+
size_t ch_len, uint8_t use_pattern) {
|
248
|
+
redis_engine_s *e = (redis_engine_s *)eng;
|
249
|
+
|
250
|
+
if (!sock_isvalid(e->sub))
|
251
|
+
return;
|
252
|
+
resp_object_s *cmd = resp_arr2obj(2, NULL);
|
253
|
+
if (!cmd)
|
254
|
+
return;
|
255
|
+
resp_obj2arr(cmd)->array[0] = use_pattern ? resp_str2obj("PUNSUBSCRIBE", 12)
|
256
|
+
: resp_str2obj("UNSUBSCRIBE", 11);
|
257
|
+
resp_obj2arr(cmd)->array[1] =
|
258
|
+
(ch ? resp_str2obj(ch, ch_len) : resp_nil2obj());
|
259
|
+
void *buffer = malloc(32 + ch_len);
|
260
|
+
size_t size = 32 + ch_len;
|
261
|
+
if (!resp_format(e->sub_parser, buffer, &size, cmd) && size <= (32 + ch_len))
|
262
|
+
sock_write2(.uuid = e->sub, .buffer = buffer, .length = size, .move = 1);
|
263
|
+
resp_free_object(cmd);
|
264
|
+
}
|
265
|
+
|
266
|
+
/** Should return 0 on success and -1 on failure. */
|
267
|
+
static int publish(const pubsub_engine_s *eng, const char *ch, size_t ch_len,
|
268
|
+
const char *msg, size_t msg_len, uint8_t use_pattern) {
|
269
|
+
if (!msg || use_pattern || !ch)
|
270
|
+
return -1;
|
271
|
+
resp_object_s *cmd = resp_arr2obj(3, NULL);
|
272
|
+
if (!cmd)
|
273
|
+
return -1;
|
274
|
+
resp_obj2arr(cmd)->array[0] = resp_str2obj("PUBLISH", 7);
|
275
|
+
resp_obj2arr(cmd)->array[1] = resp_str2obj(ch, ch_len);
|
276
|
+
resp_obj2arr(cmd)->array[2] = resp_str2obj(msg, msg_len);
|
277
|
+
redis_engine_send((pubsub_engine_s *)eng, cmd, NULL, NULL);
|
278
|
+
resp_free_object(cmd);
|
279
|
+
return 0;
|
280
|
+
}
|
281
|
+
|
282
|
+
/* *****************************************************************************
|
283
|
+
Creation / Destruction
|
284
|
+
***************************************************************************** */
|
285
|
+
|
286
|
+
static void initialize_engine(void *en_, void *ig) {
|
287
|
+
redis_engine_s *r = (redis_engine_s *)en_;
|
288
|
+
(void)ig;
|
289
|
+
connect_sub(r);
|
290
|
+
connect_pub(r);
|
291
|
+
}
|
292
|
+
|
293
|
+
/**
|
294
|
+
See the {pubsub.h} file for documentation about engines.
|
295
|
+
|
296
|
+
function names speak for themselves ;-)
|
297
|
+
*/
|
298
|
+
#undef redis_engine_create
|
299
|
+
pubsub_engine_s *redis_engine_create(struct redis_engine_create_args a) {
|
300
|
+
if (!a.port) {
|
301
|
+
return NULL;
|
302
|
+
}
|
303
|
+
size_t addr_len = a.address ? strlen(a.address) : 0;
|
304
|
+
size_t port_len = strlen(a.port);
|
305
|
+
redis_engine_s *e = malloc(sizeof(*e) + addr_len + port_len + 2);
|
306
|
+
*e = (redis_engine_s){
|
307
|
+
.engine = {.subscribe = subscribe,
|
308
|
+
.unsubscribe = unsubscribe,
|
309
|
+
.publish = publish},
|
310
|
+
.address = (char *)(e + 1),
|
311
|
+
.port = ((char *)(e + 1) + addr_len + 1),
|
312
|
+
.ref = 1,
|
313
|
+
.sub_parser = resp_parser_new(),
|
314
|
+
.pub_parser = resp_parser_new(),
|
315
|
+
.callbacks = FIO_LIST_INIT_STATIC(e->callbacks),
|
316
|
+
.active = 1,
|
317
|
+
.sub_state = 1,
|
318
|
+
.pub_state = 1,
|
319
|
+
};
|
320
|
+
if (a.address)
|
321
|
+
memcpy(e->address, a.address, addr_len);
|
322
|
+
else
|
323
|
+
e->address = NULL;
|
324
|
+
e->address[addr_len] = 0;
|
325
|
+
memcpy(e->port, a.port, port_len);
|
326
|
+
e->port[port_len] = 0;
|
327
|
+
|
328
|
+
e->sub_ctx =
|
329
|
+
redis_create_context(.parser = e->sub_parser, .auth = (char *)a.auth,
|
330
|
+
.auth_len = a.auth_len, .on_message = on_message_sub,
|
331
|
+
.on_close = on_close_sub, .on_open = on_open_sub,
|
332
|
+
.udata = e, .ping = a.ping_interval),
|
333
|
+
|
334
|
+
e->pub_ctx =
|
335
|
+
redis_create_context(.parser = e->pub_parser, .auth = (char *)a.auth,
|
336
|
+
.auth_len = a.auth_len, .on_message = on_message_pub,
|
337
|
+
.on_close = on_close_pub, .on_open = on_open_pub,
|
338
|
+
.udata = e, .ping = a.ping_interval, ),
|
339
|
+
|
340
|
+
defer(initialize_engine, e, NULL);
|
341
|
+
return (pubsub_engine_s *)e;
|
342
|
+
}
|
343
|
+
|
344
|
+
/* *****************************************************************************
|
345
|
+
Sending Data
|
346
|
+
***************************************************************************** */
|
347
|
+
|
348
|
+
/**
|
349
|
+
Sends a Redis message through the engine's connection. The response will be sent
|
350
|
+
back using the optional callback. `udata` is passed along untouched.
|
351
|
+
*/
|
352
|
+
intptr_t redis_engine_send(pubsub_engine_s *e, resp_object_s *data,
|
353
|
+
void (*callback)(pubsub_engine_s *e,
|
354
|
+
resp_object_s *reply, void *udata),
|
355
|
+
void *udata) {
|
356
|
+
if (!e || !data)
|
357
|
+
return -1;
|
358
|
+
|
359
|
+
redis_engine_s *r = (redis_engine_s *)e;
|
360
|
+
size_t len = 0;
|
361
|
+
resp_format(r->pub_parser, NULL, &len, data);
|
362
|
+
if (!len)
|
363
|
+
return -1;
|
364
|
+
|
365
|
+
callbacks_s *cb = malloc(sizeof(*cb) + len);
|
366
|
+
*cb = (callbacks_s){
|
367
|
+
.node = FIO_LIST_INIT_STATIC(cb->node),
|
368
|
+
.callback = callback,
|
369
|
+
.udata = udata,
|
370
|
+
.len = len,
|
371
|
+
};
|
372
|
+
resp_format(r->pub_parser, (uint8_t *)(cb + 1), &len, data);
|
373
|
+
spn_lock(&r->lock);
|
374
|
+
fio_list_push(callbacks_s, node, r->callbacks, cb);
|
375
|
+
spn_unlock(&r->lock);
|
376
|
+
schedule_pub_send(r, r->pub);
|
377
|
+
return 0;
|
378
|
+
}
|
379
|
+
|
380
|
+
/**
|
381
|
+
See the {pubsub.h} file for documentation about engines.
|
382
|
+
|
383
|
+
function names speak for themselves ;-)
|
384
|
+
*/
|
385
|
+
void redis_engine_destroy(pubsub_engine_s *engine) {
|
386
|
+
redis_engine_s *r = (redis_engine_s *)engine;
|
387
|
+
|
388
|
+
spn_lock(&r->lock);
|
389
|
+
callbacks_s *cb;
|
390
|
+
fio_list_for_each(callbacks_s, node, cb, r->callbacks) free(cb);
|
391
|
+
sock_force_close(r->pub);
|
392
|
+
sock_force_close(r->sub);
|
393
|
+
|
394
|
+
r->active = 0;
|
395
|
+
if (dealloc_engine(r))
|
396
|
+
return;
|
397
|
+
spn_unlock(&r->lock);
|
398
|
+
}
|