opal-up 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +209 -0
  3. data/README.md +97 -29
  4. data/bin/up_ruby +4 -0
  5. data/bin/up_ruby_cluster +4 -0
  6. data/ext/up_ext/App.h +606 -0
  7. data/ext/up_ext/AsyncSocket.h +355 -0
  8. data/ext/up_ext/AsyncSocketData.h +87 -0
  9. data/ext/up_ext/BloomFilter.h +83 -0
  10. data/ext/up_ext/ChunkedEncoding.h +236 -0
  11. data/ext/up_ext/ClientApp.h +36 -0
  12. data/ext/up_ext/HttpContext.h +502 -0
  13. data/ext/up_ext/HttpContextData.h +56 -0
  14. data/ext/up_ext/HttpErrors.h +53 -0
  15. data/ext/up_ext/HttpParser.h +680 -0
  16. data/ext/up_ext/HttpResponse.h +578 -0
  17. data/ext/up_ext/HttpResponseData.h +95 -0
  18. data/ext/up_ext/HttpRouter.h +380 -0
  19. data/ext/up_ext/Loop.h +204 -0
  20. data/ext/up_ext/LoopData.h +112 -0
  21. data/ext/up_ext/MoveOnlyFunction.h +377 -0
  22. data/ext/up_ext/PerMessageDeflate.h +315 -0
  23. data/ext/up_ext/ProxyParser.h +163 -0
  24. data/ext/up_ext/QueryParser.h +120 -0
  25. data/ext/up_ext/TopicTree.h +363 -0
  26. data/ext/up_ext/Utilities.h +66 -0
  27. data/ext/up_ext/WebSocket.h +381 -0
  28. data/ext/up_ext/WebSocketContext.h +434 -0
  29. data/ext/up_ext/WebSocketContextData.h +109 -0
  30. data/ext/up_ext/WebSocketData.h +86 -0
  31. data/ext/up_ext/WebSocketExtensions.h +256 -0
  32. data/ext/up_ext/WebSocketHandshake.h +145 -0
  33. data/ext/up_ext/WebSocketProtocol.h +506 -0
  34. data/ext/up_ext/bsd.c +767 -0
  35. data/ext/up_ext/bsd.h +109 -0
  36. data/ext/up_ext/context.c +524 -0
  37. data/ext/up_ext/epoll_kqueue.c +458 -0
  38. data/ext/up_ext/epoll_kqueue.h +67 -0
  39. data/ext/up_ext/extconf.rb +5 -0
  40. data/ext/up_ext/internal.h +224 -0
  41. data/ext/up_ext/libusockets.h +350 -0
  42. data/ext/up_ext/libuwebsockets.cpp +1344 -0
  43. data/ext/up_ext/libuwebsockets.h +396 -0
  44. data/ext/up_ext/loop.c +386 -0
  45. data/ext/up_ext/loop_data.h +38 -0
  46. data/ext/up_ext/socket.c +231 -0
  47. data/ext/up_ext/up_ext.c +930 -0
  48. data/lib/up/bun/rack_env.rb +1 -13
  49. data/lib/up/bun/server.rb +93 -19
  50. data/lib/up/cli.rb +3 -0
  51. data/lib/up/client.rb +68 -0
  52. data/lib/up/ruby/cluster.rb +39 -0
  53. data/lib/up/ruby/cluster_cli.rb +10 -0
  54. data/lib/up/{node → ruby}/rack_cluster.rb +5 -4
  55. data/lib/up/{node → ruby}/rack_server.rb +4 -4
  56. data/lib/up/ruby/server_cli.rb +10 -0
  57. data/lib/up/u_web_socket/cluster.rb +18 -3
  58. data/lib/up/u_web_socket/server.rb +108 -15
  59. data/lib/up/version.rb +1 -1
  60. metadata +72 -30
  61. data/.gitignore +0 -5
  62. data/Gemfile +0 -2
  63. data/bin/up_node +0 -12
  64. data/bin/up_node_cluster +0 -12
  65. data/example_rack_app/Gemfile +0 -3
  66. data/example_rack_app/config.ru +0 -6
  67. data/example_rack_app/rack_app.rb +0 -5
  68. data/example_roda_app/Gemfile +0 -6
  69. data/example_roda_app/config.ru +0 -6
  70. data/example_roda_app/roda_app.rb +0 -37
  71. data/example_sinatra_app/Gemfile +0 -6
  72. data/example_sinatra_app/config.ru +0 -6
  73. data/example_sinatra_app/sinatra_app.rb +0 -7
  74. data/lib/up/node/cluster.rb +0 -39
  75. data/lib/up/node/cluster_cli.rb +0 -15
  76. data/lib/up/node/rack_env.rb +0 -106
  77. data/lib/up/node/server.rb +0 -84
  78. data/lib/up/node/server_cli.rb +0 -15
  79. data/lib/up/u_web_socket/rack_env.rb +0 -101
  80. data/opal-up.gemspec +0 -27
  81. data/up_logo.svg +0 -256
@@ -0,0 +1,930 @@
1
+ #include "libusockets.h"
2
+ #include "libuwebsockets.h"
3
+ #include <arpa/inet.h>
4
+ #include <ruby.h>
5
+ #include <ruby/encoding.h>
6
+ #include <sys/socket.h>
7
+ #include <sys/wait.h>
8
+ #include <unistd.h>
9
+
10
+ #define USE_SSL 0
11
+ #define MAX_HEADER_KEY_BUF 256
12
+ #define MAX_HEADER_KEY_LEN 255
13
+ #define INTERNAL_PUBLISH_PATH "/__up__cluster__publish__"
14
+
15
+ static VALUE mUp;
16
+ static VALUE mRuby;
17
+ static VALUE cServer;
18
+ static VALUE cClient;
19
+ static VALUE cStringIO;
20
+ static VALUE cLogger;
21
+
22
+ static ID at_env;
23
+ static ID at_handler;
24
+ static ID at_member_id;
25
+ static ID at_open;
26
+ static ID at_protocol;
27
+ static ID at_secret;
28
+ static ID at_server;
29
+ static ID at_timeout;
30
+ static ID at_workers;
31
+ static ID id_app;
32
+ static ID id_call;
33
+ static ID id_close;
34
+ static ID id_each;
35
+ static ID id_host;
36
+ static ID id_logger;
37
+ static ID id_on_close;
38
+ static ID id_on_drained;
39
+ static ID id_on_message;
40
+ static ID id_on_open;
41
+ static ID id_port;
42
+
43
+ static rb_encoding *utf8_encoding;
44
+ static rb_encoding *binary_encoding;
45
+
46
+ static VALUE default_input;
47
+ static VALUE default_logger;
48
+
49
+ static VALUE rack_env_template;
50
+
51
+ static VALUE empty_string;
52
+ static VALUE http11;
53
+ static VALUE rack_input;
54
+ static VALUE rack_logger;
55
+ static VALUE rack_upgrade_q;
56
+ static VALUE rack_upgrade;
57
+ static VALUE sym_websocket;
58
+
59
+ static VALUE HTTP_VERSION;
60
+ static VALUE PATH_INFO;
61
+ static VALUE QUERY_STRING;
62
+ static VALUE REQUEST_METHOD;
63
+ static VALUE SCRIPT_NAME;
64
+ static VALUE SERVER_NAME;
65
+ static VALUE SERVER_PORT;
66
+ static VALUE SERVER_PROTOCOL;
67
+
68
+ #define set_str_val(gl_name, str) \
69
+ rb_gc_register_address(&gl_name); \
70
+ (gl_name) = rb_enc_str_new((str), strlen((str)), binary_encoding); \
71
+ rb_obj_freeze(gl_name);
72
+
73
+ #define set_sym_val(gl_name, str) \
74
+ rb_gc_register_address(&gl_name); \
75
+ (gl_name) = ID2SYM(rb_intern(str));
76
+
77
+ #define set_global(global_name) set_str_val((global_name), #global_name)
78
+
79
+ #define to_upper(c) (((c) >= 'a' && (c) <= 'z') ? ((c) & ~32) : (c))
80
+
81
+ static inline long ltoa(char *dest, long value) {
82
+ char *ptr = dest, *ptr1 = dest, tmp_char;
83
+ long tmp;
84
+
85
+ do {
86
+ tmp = value;
87
+ value /= 10;
88
+ *ptr++ = "0123456789"[(tmp - value * 10)];
89
+ } while (value);
90
+
91
+ tmp = ptr - ptr1;
92
+ *ptr-- = '\0';
93
+
94
+ while (ptr1 < ptr) {
95
+ tmp_char = *ptr;
96
+ *ptr-- = *ptr1;
97
+ *ptr1++ = tmp_char;
98
+ }
99
+ return tmp;
100
+ }
101
+
102
+ VALUE up_internal_handle_part(RB_BLOCK_CALL_FUNC_ARGLIST(rpart, res)) {
103
+ if (TYPE(rpart) == T_STRING)
104
+ uws_res_write(USE_SSL, (uws_res_t *)res, RSTRING_PTR(rpart),
105
+ RSTRING_LEN(rpart));
106
+ return Qnil;
107
+ }
108
+
109
+ typedef struct server_s {
110
+ VALUE self;
111
+ uws_app_t *app;
112
+ VALUE rapp;
113
+ VALUE host;
114
+ VALUE port;
115
+ VALUE logger;
116
+ VALUE env_template;
117
+ int workers;
118
+ int member_id;
119
+ char secret[37];
120
+ } server_s;
121
+
122
+ static void up_internal_req_header_handler(const char *h, size_t h_len,
123
+ const char *v, size_t v_len,
124
+ void *renv) {
125
+ char header_key[MAX_HEADER_KEY_BUF] = {'H', 'T', 'T', 'P', '_', '\0'};
126
+ if ((h_len + 5) > MAX_HEADER_KEY_LEN)
127
+ h_len = MAX_HEADER_KEY_LEN - 5;
128
+
129
+ for (size_t i = 0; i < h_len; ++i) {
130
+ header_key[i + 5] = (h[i] == '-') ? '_' : to_upper(h[i]);
131
+ }
132
+
133
+ header_key[h_len + 5] = '\0';
134
+ rb_hash_aset((VALUE)renv,
135
+ rb_enc_str_new(header_key, h_len + 5, binary_encoding),
136
+ rb_enc_str_new(v, v_len, binary_encoding));
137
+ }
138
+
139
+ static void up_server_prepare_env(VALUE renv, uws_req_t *req) {
140
+ // The HTTP request method, such as “GET” or “POST”. This cannot ever be an
141
+ // empty string, and so is always required.
142
+ const char *str;
143
+ size_t len = uws_req_get_method(req, &str);
144
+ char m[20];
145
+ if (len > 19)
146
+ len = 19;
147
+ for (size_t i = 0; i < len; ++i) {
148
+ m[i] = (str[i] == '-') ? '_' : to_upper(str[i]);
149
+ }
150
+ rb_hash_aset(renv, REQUEST_METHOD, rb_enc_str_new(m, len, binary_encoding));
151
+
152
+ // The remainder of the request URL’s “path”, designating the virtual
153
+ // “location” of the request’s target within the application.
154
+ len = uws_req_get_url(req, &str);
155
+ rb_hash_aset(renv, PATH_INFO, rb_enc_str_new(str, len, binary_encoding));
156
+
157
+ // The portion of the request URL that follows the ?, if any. May be empty,
158
+ // but is always required!
159
+ len = uws_req_get_query(req, NULL, 0, &str);
160
+ if (len > 0)
161
+ rb_hash_aset(renv, QUERY_STRING, rb_enc_str_new(str, len, binary_encoding));
162
+
163
+ uws_req_for_each_header(req, up_internal_req_header_handler, (void *)renv);
164
+ }
165
+
166
+ static int up_internal_res_header_handler(VALUE key, VALUE data, VALUE arg) {
167
+ char header_key[MAX_HEADER_KEY_BUF];
168
+
169
+ uws_res_t *res = (uws_res_t *)arg;
170
+ int kt = TYPE(key), dt = TYPE(data);
171
+ if (dt == T_NIL || kt == T_NIL)
172
+ return ST_CONTINUE;
173
+ if (dt == T_ARRAY) {
174
+ for (long i = 0, end = RARRAY_LEN(data); i < end; ++i) {
175
+ if (up_internal_res_header_handler(key, rb_ary_entry(data, i), arg) ==
176
+ ST_CONTINUE)
177
+ continue;
178
+ return ST_STOP;
179
+ }
180
+ return ST_CONTINUE;
181
+ }
182
+ if (kt != T_STRING) {
183
+ key = rb_obj_as_string(key);
184
+ if (TYPE(key) != T_STRING)
185
+ return ST_CONTINUE;
186
+ }
187
+ if (dt != T_STRING) {
188
+ data = rb_obj_as_string(data);
189
+ if (TYPE(data) != T_STRING)
190
+ return ST_CONTINUE;
191
+ }
192
+ char *key_s = RSTRING_PTR(key);
193
+ int key_len = RSTRING_LEN(key);
194
+ char *data_s = RSTRING_PTR(data);
195
+ int data_len = RSTRING_LEN(data);
196
+
197
+ if (key_len > MAX_HEADER_KEY_LEN)
198
+ key_len = MAX_HEADER_KEY_LEN;
199
+
200
+ for (int i = 0; i < key_len; ++i) {
201
+ header_key[i] = tolower(key_s[i]);
202
+ }
203
+
204
+ // scan the value for newline (\n) delimiters
205
+ char *pos_s = data_s;
206
+ char *pos_e = data_s + data_len;
207
+ while (pos_s < pos_e) {
208
+ // scanning for newline (\n) delimiters
209
+ char *const start = pos_s;
210
+ pos_s = memchr(pos_s, '\n', pos_e - pos_s);
211
+ if (!pos_s)
212
+ pos_s = pos_e;
213
+ uws_res_write_header(USE_SSL, res, header_key, key_len, start,
214
+ pos_s - start);
215
+ // move forward (skip the '\n' if exists)
216
+ ++pos_s;
217
+ }
218
+
219
+ // no errors, return 0
220
+ return ST_CONTINUE;
221
+ RB_GC_GUARD(key);
222
+ RB_GC_GUARD(data);
223
+ }
224
+
225
+ static bool up_internal_set_response_status(uws_res_t *res, VALUE rstatus) {
226
+ char status[10];
227
+ int type = TYPE(rstatus);
228
+ long a_long;
229
+ if (type == T_FIXNUM) {
230
+ a_long = FIX2INT(rstatus);
231
+ if (a_long < 0 || a_long > 999)
232
+ return false;
233
+ a_long = ltoa(status, a_long);
234
+ } else if (type == T_STRING) {
235
+ a_long = RSTRING_LEN(rstatus);
236
+ if (a_long > 6)
237
+ a_long = 6;
238
+ memcpy(status, RSTRING_PTR(rstatus), a_long);
239
+ } else {
240
+ return false;
241
+ }
242
+ memcpy(status + a_long, " OK", 4); // copy the '\0' too
243
+ uws_res_write_status(USE_SSL, res, status, a_long + 3);
244
+ return true;
245
+ }
246
+
247
+ static bool up_internal_collect_response_body(uws_res_t *res, VALUE rparts) {
248
+ VALUE rpart;
249
+ if (TYPE(rparts) == T_ARRAY) {
250
+ long i, l = RARRAY_LEN(rparts);
251
+ for (i = 0; i < l; i++) {
252
+ rpart = rb_ary_entry(rparts, i);
253
+ if (TYPE(rpart) != T_STRING)
254
+ return false;
255
+ uws_res_write(USE_SSL, res, RSTRING_PTR(rpart), RSTRING_LEN(rpart));
256
+ }
257
+ } else if (rb_respond_to(rparts, id_each)) {
258
+ rb_block_call(rparts, id_each, 0, NULL, up_internal_handle_part,
259
+ (VALUE)res);
260
+ } else if (rb_respond_to(rparts, id_call)) {
261
+ rpart = rb_funcall(rparts, id_call, 0);
262
+ if (TYPE(rpart) != T_STRING)
263
+ return false;
264
+ uws_res_write(USE_SSL, res, RSTRING_PTR(rpart), RSTRING_LEN(rpart));
265
+ } else {
266
+ return false;
267
+ }
268
+ return true;
269
+ }
270
+
271
+ typedef struct publish_data_s {
272
+ int pos;
273
+ const char *data[2];
274
+ size_t lengths[2];
275
+ server_s *s;
276
+ } publish_data_s;
277
+
278
+ static void up_internal_process_publish_post_data(uws_res_t *res,
279
+ const char *chunk,
280
+ size_t chunk_length,
281
+ bool is_end, void *arg) {
282
+ server_s *s = (server_s *)arg;
283
+ const char *channel_start = chunk, *chunk_ptr = chunk,
284
+ *chunk_end = chunk + chunk_length, *message_start = NULL;
285
+ size_t channel_length = 0, message_length = 0;
286
+ for (; chunk_ptr < chunk_end; chunk_ptr++) {
287
+ if (*chunk_ptr == '\r') {
288
+ channel_length = chunk_ptr - chunk;
289
+ message_start = chunk + channel_length + 2;
290
+ message_length = chunk_length - 2 - channel_length;
291
+ break;
292
+ }
293
+ }
294
+ if (channel_length > 0 && message_length > 0) {
295
+ uws_publish(USE_SSL, s->app, channel_start, channel_length, message_start,
296
+ message_length, TEXT, false);
297
+ }
298
+ }
299
+
300
+ static void up_internal_publish_handler(uws_res_t *res, uws_req_t *req,
301
+ void *arg) {
302
+ server_s *s = (server_s *)arg;
303
+ // check for header
304
+ const char *secret;
305
+ uws_req_get_header(req, "secret", 6, &secret);
306
+ if (secret && (strncmp(s->secret, secret, 36) == 0)) {
307
+ // ok, requests origin knows the secret, continue processing
308
+ uws_res_on_data(false, res, up_internal_process_publish_post_data, arg);
309
+ uws_res_write_status(false, res, "200 OK", 6);
310
+ uws_res_end_without_body(false, res, true);
311
+ } else {
312
+ // don't know the secret? bugger off!
313
+ uws_res_end_without_body(false, res, true);
314
+ }
315
+ }
316
+
317
+ static void up_server_request_handler(uws_res_t *res, uws_req_t *req,
318
+ void *arg) {
319
+ // prepare rack env
320
+ server_s *s = (server_s *)arg;
321
+ VALUE renv = rb_hash_dup(s->env_template);
322
+ up_server_prepare_env(renv, req);
323
+
324
+ // call app
325
+ VALUE rres = rb_funcall(s->rapp, id_call, 1, renv);
326
+ if (TYPE(rres) != T_ARRAY)
327
+ goto response_error;
328
+
329
+ // response status
330
+ VALUE rstatus = rb_ary_entry(rres, 0);
331
+ if (!up_internal_set_response_status(res, rstatus))
332
+ goto response_error;
333
+
334
+ // collect headers
335
+ VALUE rheaders = rb_ary_entry(rres, 1);
336
+ if (TYPE(rheaders) != T_HASH)
337
+ goto response_error;
338
+ rb_hash_foreach(rheaders, up_internal_res_header_handler, (VALUE)res);
339
+
340
+ // collect response body
341
+ VALUE rparts = rb_ary_entry(rres, 2);
342
+ up_internal_collect_response_body(res, rparts);
343
+
344
+ // end response
345
+ uws_res_end_without_body(USE_SSL, res, false);
346
+
347
+ // close resources if necessary
348
+ if (rb_respond_to(rparts, id_close))
349
+ rb_funcall(rparts, id_close, 0);
350
+
351
+ return;
352
+ RB_GC_GUARD(rstatus);
353
+ RB_GC_GUARD(rheaders);
354
+ RB_GC_GUARD(rres);
355
+ RB_GC_GUARD(renv);
356
+ response_error:
357
+ fprintf(stderr, "response error\n");
358
+ uws_res_end_without_body(USE_SSL, res, false);
359
+ }
360
+
361
+ static void up_server_listen_handler(struct us_listen_socket_t *listen_socket,
362
+ uws_app_listen_config_t config,
363
+ void *user_data) {
364
+ if (listen_socket)
365
+ fprintf(stderr, "Server is running on http://%s:%d\n", config.host,
366
+ config.port);
367
+ }
368
+
369
+ const rb_data_type_t up_client_t = {.wrap_struct_name = "Up::Client",
370
+ .function = {.dmark = NULL,
371
+ .dfree = NULL,
372
+ .dsize = NULL,
373
+ .dcompact = NULL,
374
+ .reserved = {0}},
375
+ .parent = NULL,
376
+ .data = NULL,
377
+ .flags = 0};
378
+
379
+ static VALUE up_client_alloc(VALUE rclass) {
380
+ return TypedData_Wrap_Struct(rclass, &up_client_t, NULL);
381
+ }
382
+
383
+ static VALUE up_client_close(VALUE self) {
384
+ uws_websocket_t *ws = DATA_PTR(self);
385
+ rb_ivar_set(self, at_open, Qfalse);
386
+ if (ws)
387
+ uws_ws_close(USE_SSL, ws);
388
+ return Qnil;
389
+ }
390
+
391
+ static VALUE up_client_pending(VALUE self) {
392
+ uws_websocket_t *ws = DATA_PTR(self);
393
+ if (ws)
394
+ return INT2FIX(uws_ws_get_buffered_amount(USE_SSL, ws));
395
+ return INT2FIX(0);
396
+ }
397
+
398
+ static void up_client_cluster_publish(server_s *s, int st, VALUE channel,
399
+ VALUE message) {
400
+ const char *opening_line = "POST " INTERNAL_PUBLISH_PATH " HTTP/1.1\r\n";
401
+ const char *host_header = "Host: localhost\r\n";
402
+ const char *secret = "Secret: ";
403
+ char secret_header[50];
404
+ memcpy(secret_header, secret, 8);
405
+ memcpy(secret_header + 8, s->secret, 36);
406
+ memcpy(secret_header + 8 + 36, "\r\n", 2);
407
+ const char *content_type = "Content-Type: text/plain\r\n";
408
+ long c_length = RSTRING_LEN(channel) + RSTRING_LEN(message) + 2;
409
+ char content_length[50];
410
+ memcpy(content_length, "Content-Length: ", 16);
411
+ long cl = ltoa(content_length + 16, c_length);
412
+ memcpy(content_length + 16 + cl, "\r\n\r\n", 4);
413
+ const char *boundary_disposition = "\r\n";
414
+
415
+ send(st, opening_line, strlen(opening_line), MSG_DONTROUTE | MSG_MORE);
416
+ send(st, host_header, strlen(host_header), MSG_DONTROUTE | MSG_MORE);
417
+ send(st, secret_header, 46, MSG_DONTROUTE | MSG_MORE);
418
+ send(st, content_type, strlen(content_type), MSG_DONTROUTE | MSG_MORE);
419
+ send(st, content_length, strlen(content_length), MSG_DONTROUTE | MSG_MORE);
420
+ send(st, RSTRING_PTR(channel), RSTRING_LEN(channel),
421
+ MSG_DONTROUTE | MSG_MORE);
422
+ send(st, boundary_disposition, strlen(boundary_disposition),
423
+ MSG_DONTROUTE | MSG_MORE);
424
+ send(st, RSTRING_PTR(message), RSTRING_LEN(message),
425
+ MSG_DONTROUTE | MSG_MORE);
426
+
427
+ // char read_buf[256];
428
+ // if (read(st, read_buf, 256)) {
429
+ // // do nothing
430
+ // };
431
+ // fprintf(stderr, "read: %s\n", read_buf);
432
+ }
433
+
434
+ static VALUE up_client_publish(int argc, VALUE *argv, VALUE self) {
435
+ uws_websocket_t *ws = DATA_PTR(self);
436
+ if (!ws)
437
+ return Qnil;
438
+ VALUE channel, message, engine;
439
+ rb_scan_args(argc, argv, "21", &channel, &message, &engine);
440
+ if (TYPE(channel) != T_STRING)
441
+ channel = rb_obj_as_string(channel);
442
+ if (TYPE(message) != T_STRING)
443
+ message = rb_obj_as_string(message);
444
+ VALUE server = rb_ivar_get(self, at_server);
445
+ if (server != Qnil) {
446
+ server_s *s = DATA_PTR(server);
447
+ int res =
448
+ uws_publish(USE_SSL, s->app, RSTRING_PTR(channel), RSTRING_LEN(channel),
449
+ RSTRING_PTR(message), RSTRING_LEN(message), TEXT, false);
450
+ if (s->member_id > 0) {
451
+
452
+ // publish to cluster members
453
+ int i;
454
+ struct sockaddr_in member_addr = {
455
+ .sin_addr.s_addr = inet_addr("127.0.0.1"), .sin_family = AF_INET};
456
+ for (i = 1; i <= s->workers; i++) {
457
+ if (i != s->member_id) {
458
+ int st = socket(AF_INET, SOCK_STREAM, 0);
459
+ if (st) {
460
+ member_addr.sin_port = htons(FIX2INT(s->port) + i);
461
+ if (connect(st, (struct sockaddr *)&member_addr,
462
+ sizeof(struct sockaddr_in)) == 0) {
463
+ up_client_cluster_publish(s, st, channel, message);
464
+ close(st);
465
+ }
466
+ }
467
+ }
468
+ }
469
+ }
470
+ return res ? Qtrue : Qfalse;
471
+ }
472
+ return Qfalse;
473
+ }
474
+
475
+ static VALUE up_client_subscribe(int argc, VALUE *argv, VALUE self) {
476
+ uws_websocket_t *ws = DATA_PTR(self);
477
+ if (!ws)
478
+ return Qnil;
479
+ VALUE channel, is_pattern;
480
+ rb_scan_args(argc, argv, "11", &channel, &is_pattern);
481
+ if (TYPE(channel) != T_STRING)
482
+ channel = rb_obj_as_string(channel);
483
+ return uws_ws_subscribe(USE_SSL, ws, RSTRING_PTR(channel),
484
+ RSTRING_LEN(channel))
485
+ ? Qtrue
486
+ : Qnil;
487
+ }
488
+
489
+ static VALUE up_client_write(VALUE self, VALUE rdata) {
490
+ uws_websocket_t *ws = DATA_PTR(self);
491
+ if (!ws)
492
+ rb_raise(rb_eStandardError, "socket closed, can't write");
493
+ if (TYPE(rdata) != T_STRING)
494
+ rdata = rb_obj_as_string(rdata);
495
+ if (TYPE(rdata) != T_STRING)
496
+ rb_raise(rb_eTypeError,
497
+ "rdata not a string or cannot be converted to a string");
498
+ int opcode = rb_enc_get(rdata) == binary_encoding ? BINARY : TEXT;
499
+ return INT2FIX(
500
+ uws_ws_send(USE_SSL, ws, RSTRING_PTR(rdata), RSTRING_LEN(rdata), opcode));
501
+ }
502
+
503
+ static VALUE up_client_unsubscribe(int argc, VALUE *argv, VALUE self) {
504
+ uws_websocket_t *ws = DATA_PTR(self);
505
+ if (!ws)
506
+ return Qnil;
507
+ VALUE channel, is_pattern;
508
+ rb_scan_args(argc, argv, "11", &channel, &is_pattern);
509
+ if (TYPE(channel) != T_STRING)
510
+ channel = rb_obj_as_string(channel);
511
+ return uws_ws_unsubscribe(USE_SSL, ws, RSTRING_PTR(channel),
512
+ RSTRING_LEN(channel))
513
+ ? Qtrue
514
+ : Qnil;
515
+ }
516
+
517
+ static void up_server_t_free(void *p) {
518
+ server_s *s = (server_s *)p;
519
+ rb_gc_unregister_address(&s->host);
520
+ rb_gc_unregister_address(&s->port);
521
+ rb_gc_unregister_address(&s->env_template);
522
+ free(s);
523
+ }
524
+
525
+ const rb_data_type_t up_server_t = {.wrap_struct_name = "Up::Ruby::Server",
526
+ .function = {.dmark = NULL,
527
+ .dfree = up_server_t_free,
528
+ .dsize = NULL,
529
+ .dcompact = NULL,
530
+ .reserved = {0}},
531
+ .parent = NULL,
532
+ .data = NULL,
533
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY};
534
+
535
+ static VALUE up_server_alloc(VALUE rclass) {
536
+ server_s *s = calloc(1, sizeof(server_s));
537
+ if (!s)
538
+ rb_raise(rb_eNoMemError, "unable to allocate server");
539
+ rb_gc_register_address(&s->host);
540
+ rb_gc_register_address(&s->port);
541
+ rb_gc_register_address(&s->env_template);
542
+ return s->self = TypedData_Wrap_Struct(rclass, &up_server_t, s);
543
+ }
544
+
545
+ static void up_internal_check_arg_types(VALUE rapp, VALUE *rhost,
546
+ VALUE *rport) {
547
+ if (!rb_respond_to(rapp, id_call))
548
+ rb_raise(rb_eArgError, "app does not respond to #call");
549
+ if (*rhost == Qundef || *rhost == Qnil) {
550
+ *rhost = rb_str_new("localhost", 9);
551
+ }
552
+ Check_Type(*rhost, T_STRING);
553
+ if (*rport == Qundef || *rport == Qnil) {
554
+ *rport = INT2FIX(3000);
555
+ }
556
+ Check_Type(*rport, T_FIXNUM);
557
+ }
558
+
559
+ static VALUE up_server_init(int argc, VALUE *argv, VALUE self) {
560
+ if (!rb_keyword_given_p())
561
+ rb_raise(rb_eArgError, "no args given, must at least provide app:");
562
+ ID kwargs[] = {id_app, id_host, id_port, id_logger};
563
+ VALUE rargs[4] = {Qnil, Qnil, Qnil, Qnil};
564
+ VALUE options = Qnil;
565
+
566
+ rb_scan_args_kw(1, argc, argv, ":", &options);
567
+ rb_get_kwargs(options, kwargs, 1, 2, rargs);
568
+
569
+ VALUE rapp = rargs[0];
570
+ VALUE rhost = rargs[1];
571
+ VALUE rport = rargs[2];
572
+
573
+ up_internal_check_arg_types(rapp, &rhost, &rport);
574
+
575
+ server_s *s = DATA_PTR(self);
576
+ s->rapp = rapp;
577
+ s->host = rb_obj_freeze(rhost);
578
+ s->port = rport;
579
+ s->logger = rargs[3];
580
+
581
+ return self;
582
+ }
583
+
584
+ void up_ws_drain_handler(uws_websocket_t *ws, void *user_data) {
585
+ VALUE *client = (VALUE *)uws_ws_get_user_data(USE_SSL, ws);
586
+ DATA_PTR(*client) = ws;
587
+ VALUE rhandler = rb_ivar_get(*client, at_handler);
588
+ if (rb_respond_to(rhandler, id_on_drained))
589
+ rb_funcall(rhandler, id_on_drained, 1, *client);
590
+ DATA_PTR(*client) = NULL;
591
+ }
592
+
593
+ void up_ws_ping_handler(uws_websocket_t *ws, const char *message, size_t length,
594
+ void *user_data) {
595
+ /* You don't need to handle this one, we automatically respond to pings as per
596
+ * standard */
597
+ }
598
+
599
+ void up_ws_pong_handler(uws_websocket_t *ws, const char *message, size_t length,
600
+ void *user_data) {
601
+ /* You don't need to handle this one either */
602
+ }
603
+
604
+ static void up_ws_close_handler(uws_websocket_t *ws, int code,
605
+ const char *message, size_t length,
606
+ void *user_data) {
607
+ VALUE *client = (VALUE *)uws_ws_get_user_data(USE_SSL, ws);
608
+ rb_ivar_set(*client, at_open, Qfalse);
609
+ DATA_PTR(*client) = ws;
610
+ VALUE rhandler = rb_ivar_get(*client, at_handler);
611
+ if (rb_respond_to(rhandler, id_on_close))
612
+ rb_funcall(rhandler, id_on_close, 1, *client);
613
+ // rb_gc_unregister_address(client);
614
+ DATA_PTR(*client) = NULL;
615
+ free(client);
616
+ }
617
+
618
+ static void up_ws_message_handler(uws_websocket_t *ws, const char *message,
619
+ size_t length, uws_opcode_t opcode,
620
+ void *user_data) {
621
+ VALUE rmessage;
622
+ if (opcode == TEXT) {
623
+ rmessage = rb_enc_str_new(message, length, utf8_encoding);
624
+ } else if (opcode == BINARY) {
625
+ rmessage = rb_enc_str_new(message, length, binary_encoding);
626
+ } else {
627
+ return;
628
+ }
629
+ VALUE *client = (VALUE *)uws_ws_get_user_data(USE_SSL, ws);
630
+ DATA_PTR(*client) = ws;
631
+ VALUE rhandler = rb_ivar_get(*client, at_handler);
632
+ if (rb_respond_to(rhandler, id_on_message))
633
+ rb_funcall(rhandler, id_on_message, 2, *client, rmessage);
634
+ DATA_PTR(*client) = NULL;
635
+ }
636
+
637
+ static void up_ws_open_handler(uws_websocket_t *ws, void *user_data) {
638
+ VALUE *client = (VALUE *)uws_ws_get_user_data(USE_SSL, ws);
639
+ rb_ivar_set(*client, at_open, Qtrue);
640
+ DATA_PTR(*client) = ws;
641
+ VALUE rhandler = rb_ivar_get(*client, at_handler);
642
+ if (rb_respond_to(rhandler, id_on_open))
643
+ rb_funcall(rhandler, id_on_open, 1, *client);
644
+ DATA_PTR(*client) = NULL;
645
+ }
646
+
647
+ static void up_ws_upgrade_handler(uws_res_t *res, uws_req_t *req,
648
+ uws_socket_context_t *context, void *arg) {
649
+ server_s *s = (server_s *)arg;
650
+ // prepare rack env
651
+ VALUE renv = rb_hash_dup(s->env_template);
652
+ up_server_prepare_env(renv, req);
653
+ rb_hash_aset(renv, rack_upgrade_q, sym_websocket);
654
+
655
+ // call app
656
+ VALUE rres = rb_funcall(s->rapp, id_call, 1, renv);
657
+
658
+ if (TYPE(rres) != T_ARRAY)
659
+ goto upgrade_error;
660
+
661
+ // response status
662
+ VALUE rstatus = rb_ary_entry(rres, 0);
663
+ int st = FIX2INT(rstatus);
664
+
665
+ VALUE rhandler = rb_hash_lookup2(renv, rack_upgrade, Qundef);
666
+ if (st >= 0 && st < 300 && rhandler != Qundef && rhandler != Qnil) {
667
+ // upgrade
668
+
669
+ VALUE *client = malloc(sizeof(VALUE));
670
+ // rb_gc_register_address(client);
671
+ *client = rb_class_new_instance(0, NULL, cClient);
672
+ rb_ivar_set(*client, at_env, renv);
673
+ rb_ivar_set(*client, at_open, false);
674
+ rb_ivar_set(*client, at_handler, rhandler);
675
+ rb_ivar_set(*client, at_protocol, sym_websocket);
676
+ rb_ivar_set(*client, at_timeout, INT2FIX(120));
677
+ rb_ivar_set(*client, at_server, s->self);
678
+
679
+ const char *ws_key = NULL;
680
+ const char *ws_protocol = NULL;
681
+ const char *ws_extensions = NULL;
682
+ size_t ws_key_length =
683
+ uws_req_get_header(req, "sec-websocket-key", 17, &ws_key);
684
+ size_t ws_protocol_length =
685
+ uws_req_get_header(req, "sec-websocket-protocol", 22, &ws_protocol);
686
+ size_t ws_extensions_length =
687
+ uws_req_get_header(req, "sec-websocket-extensions", 24, &ws_extensions);
688
+ uws_res_upgrade(USE_SSL, res, (void *)client, ws_key, ws_key_length,
689
+ ws_protocol, ws_protocol_length, ws_extensions,
690
+ ws_extensions_length, context);
691
+ } else {
692
+ // treat as normal request
693
+ // response status
694
+ if (!up_internal_set_response_status(res, rstatus))
695
+ goto upgrade_error;
696
+
697
+ // collect headers
698
+ VALUE rheaders = rb_ary_entry(rres, 1);
699
+ if (TYPE(rheaders) != T_HASH)
700
+ goto upgrade_error;
701
+ rb_hash_foreach(rheaders, up_internal_res_header_handler, (VALUE)res);
702
+
703
+ // collect response body
704
+ VALUE rparts = rb_ary_entry(rres, 2);
705
+ up_internal_collect_response_body(res, rparts);
706
+
707
+ // end response
708
+ uws_res_end_without_body(USE_SSL, res, false);
709
+
710
+ // close resources if necessary
711
+ if (rb_respond_to(rparts, id_close))
712
+ rb_funcall(rparts, id_close, 0);
713
+
714
+ RB_GC_GUARD(rheaders);
715
+ }
716
+ return;
717
+ RB_GC_GUARD(rstatus);
718
+ RB_GC_GUARD(rres);
719
+ RB_GC_GUARD(renv);
720
+ upgrade_error:
721
+ fprintf(stderr, "upgrade error");
722
+ }
723
+
724
+ static VALUE up_server_listen(VALUE self) {
725
+ server_s *s = DATA_PTR(self);
726
+ up_internal_check_arg_types(s->rapp, &s->host, &s->port);
727
+
728
+ s->env_template = rb_hash_dup(rack_env_template);
729
+ // When combined with SCRIPT_NAME and PATH_INFO, these variables can be used
730
+ // to complete the URL.
731
+ rb_hash_aset(s->env_template, SERVER_NAME, s->host);
732
+ // An optional Integer which is the port the server is running on.
733
+ rb_hash_aset(s->env_template, SERVER_PORT, s->port);
734
+ if (s->logger && s->logger != Qundef && s->logger != Qnil) {
735
+ rb_hash_aset(s->env_template, rack_logger, s->logger);
736
+ }
737
+ struct us_socket_context_options_t options = {.key_file_name = NULL,
738
+ .cert_file_name = NULL,
739
+ .ca_file_name = NULL,
740
+ .passphrase = NULL,
741
+ .dh_params_file_name = NULL,
742
+ .ssl_ciphers = NULL};
743
+ s->app = uws_create_app(USE_SSL, options);
744
+ if (!s->app)
745
+ rb_raise(rb_eRuntimeError, "could not init uws app");
746
+ uws_app_listen_config_t config = {
747
+ .port = FIX2INT(s->port), .host = RSTRING_PTR(s->host), .options = 0};
748
+ VALUE rmember_id = rb_ivar_get(self, at_member_id);
749
+ if (rmember_id != Qnil) {
750
+ s->member_id = FIX2INT(rmember_id);
751
+ // got a cluster, open publish ports
752
+ VALUE rworkers = rb_ivar_get(self, at_workers);
753
+ s->workers = FIX2INT(rworkers);
754
+ VALUE rsecret = rb_ivar_get(self, at_secret);
755
+ if (TYPE(rsecret) != T_STRING || RSTRING_LEN(rsecret) != 36)
756
+ rb_raise(rb_eTypeError, "cluster secret of unknown type");
757
+ memcpy(s->secret, RSTRING_PTR(rsecret), 36);
758
+ s->secret[36] = '\0';
759
+ uws_app_any(USE_SSL, s->app, INTERNAL_PUBLISH_PATH,
760
+ up_internal_publish_handler, (void *)s);
761
+ uws_app_listen_config_t config_internal = {
762
+ .port = config.port + s->member_id, .host = "localhost", .options = 0};
763
+ uws_app_listen_with_config(false, s->app, config_internal,
764
+ up_server_listen_handler, NULL);
765
+ }
766
+ uws_app_any(USE_SSL, s->app, "/*", up_server_request_handler, (void *)s);
767
+ uws_ws(USE_SSL, s->app, "/*",
768
+ (uws_socket_behavior_t){.compression = DISABLED,
769
+ .maxPayloadLength = 5 * 1024 * 1024,
770
+ .idleTimeout = 120,
771
+ .upgrade = up_ws_upgrade_handler,
772
+ .open = up_ws_open_handler,
773
+ .message = up_ws_message_handler,
774
+ .close = up_ws_close_handler,
775
+ .drain = up_ws_drain_handler,
776
+ .ping = up_ws_ping_handler,
777
+ .pong = up_ws_pong_handler},
778
+ s);
779
+ uws_app_listen_with_config(USE_SSL, s->app, config, up_server_listen_handler,
780
+ NULL);
781
+ uws_app_run(USE_SSL, s->app);
782
+ return self;
783
+ }
784
+
785
+ static VALUE up_server_stop(VALUE self) {
786
+ server_s *s = DATA_PTR(self);
787
+ if (!s->app)
788
+ rb_raise(rb_eRuntimeError, "no uws, did initialize call super?");
789
+ uws_app_close(USE_SSL, s->app);
790
+ uws_app_destroy(USE_SSL, s->app);
791
+ s->app = NULL;
792
+ return Qnil;
793
+ }
794
+
795
+ void up_hash_set(VALUE rhash, const char *key, VALUE val) {
796
+ rb_hash_aset(rhash, rb_enc_str_new(key, strlen(key), binary_encoding), val);
797
+ }
798
+
799
+ void up_setup_rack_env_template(void) {
800
+ rb_gc_register_address(&rack_env_template);
801
+ rack_env_template = rb_hash_new();
802
+
803
+ // error stream
804
+ up_hash_set(rack_env_template, "rack.errors", rb_stderr);
805
+
806
+ // if present, an object responding to call that is used to perform a full
807
+ // hijack. up_hash_set(rack_env_template, "rack.hijack", Qnil);
808
+
809
+ // if present and true, indicates that the server supports partial hijacking
810
+ // up_hash_set(rack_env_template, "rack.hijack?", Qfalse);
811
+
812
+ // The input stream is an IO-like object which contains the raw HTTP POST
813
+ // data
814
+ rb_hash_aset(rack_env_template, rack_input, default_input);
815
+
816
+ // A common object interface for logging messages
817
+ up_hash_set(rack_env_template, "rack.logger", default_logger);
818
+
819
+ // An Integer hint to the multipart parser as to what chunk size to use for
820
+ // reads and writes.
821
+ up_hash_set(rack_env_template, "rack.multipart.buffer_size", INT2FIX(4096));
822
+
823
+ // An object responding to #call with two arguments, the filename and
824
+ // content_type given for the multipart form field, and returning an IO-like
825
+ // object that responds to #<< and optionally #rewind.
826
+ // up_hash_set(rack_env_template, "rack.multipart.tempfile_factory", Qnil);
827
+
828
+ // An array of callables run by the server after the response has been
829
+ // processed.
830
+ // up_hash_set(rack_env_template, "rack.response_finished", Qnil);
831
+
832
+ // A hash-like interface for storing request session data.
833
+ // up_hash_set(rack_env_template, "rack.session", Qnil);
834
+
835
+ // http or https, depending on the request URL.
836
+ up_hash_set(rack_env_template, "rack.url_scheme",
837
+ rb_enc_str_new_cstr("http", binary_encoding));
838
+
839
+ // The portion of the request URL that follows the ?, if any. May be empty,
840
+ // but is always required!
841
+ rb_hash_aset(rack_env_template, QUERY_STRING, empty_string);
842
+
843
+ // The initial portion of the request URL’s “path” that corresponds to the
844
+ // application object, so that the application knows its virtual “location”.
845
+ // This may be an empty string, if the application corresponds to the “root”
846
+ // of the server.
847
+ rb_hash_aset(rack_env_template, SCRIPT_NAME, empty_string);
848
+
849
+ // A string representing the HTTP version used for the request.
850
+ // Note: uws has no way to get that information from the request
851
+ // so set it to a static value
852
+ rb_hash_aset(rack_env_template, SERVER_PROTOCOL, http11);
853
+ rb_hash_aset(rack_env_template, HTTP_VERSION, http11);
854
+ }
855
+
856
+ void Init_up_ext(void) {
857
+ at_env = rb_intern("@env");
858
+ at_handler = rb_intern("@handler");
859
+ at_member_id = rb_intern("@member_id");
860
+ at_open = rb_intern("@open");
861
+ at_protocol = rb_intern("@protocol");
862
+ at_secret = rb_intern("@secret");
863
+ at_server = rb_intern("@server");
864
+ at_timeout = rb_intern("@timeout");
865
+ at_workers = rb_intern("@workers");
866
+ id_app = rb_intern("app");
867
+ id_call = rb_intern("call");
868
+ id_close = rb_intern("close");
869
+ id_each = rb_intern("each");
870
+ id_host = rb_intern("host");
871
+ id_logger = rb_intern("logger");
872
+ id_on_close = rb_intern("on_close");
873
+ id_on_drained = rb_intern("on_drained");
874
+ id_on_message = rb_intern("on_message");
875
+ id_on_open = rb_intern("on_open");
876
+ id_port = rb_intern("port");
877
+
878
+ utf8_encoding = rb_enc_find("UTF-8");
879
+ binary_encoding = rb_enc_find("binary");
880
+
881
+ set_str_val(empty_string, "");
882
+ set_str_val(http11, "HTTP/1.1");
883
+ set_str_val(rack_input, "rack.input");
884
+ set_str_val(rack_logger, "rack.logger");
885
+ set_str_val(rack_upgrade, "rack.upgrade");
886
+ set_str_val(rack_upgrade_q, "rack.upgrade?");
887
+ set_sym_val(sym_websocket, "websocket");
888
+ set_global(HTTP_VERSION);
889
+ set_global(PATH_INFO);
890
+ set_global(QUERY_STRING);
891
+ set_global(REQUEST_METHOD);
892
+ set_global(SCRIPT_NAME);
893
+ set_global(SERVER_NAME);
894
+ set_global(SERVER_PORT);
895
+ set_global(SERVER_PROTOCOL);
896
+
897
+ rb_require("logger");
898
+
899
+ rb_gc_register_address(&cLogger);
900
+ cLogger = rb_const_get(rb_cObject, rb_intern("Logger"));
901
+ rb_gc_register_address(&default_logger);
902
+ default_logger = rb_funcall(cLogger, rb_intern("new"), 1, rb_stderr);
903
+
904
+ rb_require("stringio");
905
+
906
+ rb_gc_register_address(&cStringIO);
907
+ cStringIO = rb_const_get(rb_cObject, rb_intern("StringIO"));
908
+ rb_gc_register_address(&default_input);
909
+ default_input = rb_funcall(cStringIO, rb_intern("new"), 1, empty_string);
910
+
911
+ up_setup_rack_env_template();
912
+
913
+ mUp = rb_define_module("Up");
914
+ cClient = rb_define_class_under(mUp, "Client", rb_cObject);
915
+ rb_define_alloc_func(cClient, up_client_alloc);
916
+ rb_define_method(cClient, "close", up_client_close, 0);
917
+ rb_define_method(cClient, "pending", up_client_pending, 0);
918
+ rb_define_method(cClient, "publish", up_client_publish, -1);
919
+ rb_define_method(cClient, "subscribe", up_client_subscribe, -1);
920
+ rb_define_method(cClient, "unsubscribe", up_client_unsubscribe, -1);
921
+ rb_define_method(cClient, "write", up_client_write, 1);
922
+
923
+ mRuby = rb_define_module_under(mUp, "Ruby");
924
+
925
+ cServer = rb_define_class_under(mRuby, "Server", rb_cObject);
926
+ rb_define_alloc_func(cServer, up_server_alloc);
927
+ rb_define_method(cServer, "initialize", up_server_init, -1);
928
+ rb_define_method(cServer, "listen", up_server_listen, 0);
929
+ rb_define_method(cServer, "stop", up_server_stop, 0);
930
+ }