opal-up 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }