isomorfeus-iodine 0.7.44

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +32 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1038 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +44 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/config.ru +56 -0
  25. data/examples/echo.ru +59 -0
  26. data/examples/hello.ru +29 -0
  27. data/examples/pubsub_engine.ru +81 -0
  28. data/examples/redis.ru +70 -0
  29. data/examples/shootout.ru +73 -0
  30. data/examples/sub-protocols.ru +90 -0
  31. data/examples/tcp_client.rb +66 -0
  32. data/examples/x-sendfile.ru +14 -0
  33. data/exe/iodine +277 -0
  34. data/ext/iodine/extconf.rb +109 -0
  35. data/ext/iodine/fio.c +11985 -0
  36. data/ext/iodine/fio.h +6373 -0
  37. data/ext/iodine/fio_cli.c +431 -0
  38. data/ext/iodine/fio_cli.h +189 -0
  39. data/ext/iodine/fio_json_parser.h +687 -0
  40. data/ext/iodine/fio_siphash.c +157 -0
  41. data/ext/iodine/fio_siphash.h +37 -0
  42. data/ext/iodine/fio_tls.h +129 -0
  43. data/ext/iodine/fio_tls_missing.c +649 -0
  44. data/ext/iodine/fio_tls_openssl.c +1056 -0
  45. data/ext/iodine/fio_tmpfile.h +50 -0
  46. data/ext/iodine/fiobj.h +44 -0
  47. data/ext/iodine/fiobj4fio.h +21 -0
  48. data/ext/iodine/fiobj_ary.c +333 -0
  49. data/ext/iodine/fiobj_ary.h +139 -0
  50. data/ext/iodine/fiobj_data.c +1185 -0
  51. data/ext/iodine/fiobj_data.h +167 -0
  52. data/ext/iodine/fiobj_hash.c +409 -0
  53. data/ext/iodine/fiobj_hash.h +176 -0
  54. data/ext/iodine/fiobj_json.c +622 -0
  55. data/ext/iodine/fiobj_json.h +68 -0
  56. data/ext/iodine/fiobj_mem.h +71 -0
  57. data/ext/iodine/fiobj_mustache.c +317 -0
  58. data/ext/iodine/fiobj_mustache.h +62 -0
  59. data/ext/iodine/fiobj_numbers.c +344 -0
  60. data/ext/iodine/fiobj_numbers.h +127 -0
  61. data/ext/iodine/fiobj_str.c +433 -0
  62. data/ext/iodine/fiobj_str.h +172 -0
  63. data/ext/iodine/fiobject.c +620 -0
  64. data/ext/iodine/fiobject.h +654 -0
  65. data/ext/iodine/hpack.h +1923 -0
  66. data/ext/iodine/http.c +2754 -0
  67. data/ext/iodine/http.h +1002 -0
  68. data/ext/iodine/http1.c +912 -0
  69. data/ext/iodine/http1.h +29 -0
  70. data/ext/iodine/http1_parser.h +873 -0
  71. data/ext/iodine/http_internal.c +1278 -0
  72. data/ext/iodine/http_internal.h +237 -0
  73. data/ext/iodine/http_mime_parser.h +350 -0
  74. data/ext/iodine/iodine.c +1430 -0
  75. data/ext/iodine/iodine.h +63 -0
  76. data/ext/iodine/iodine_caller.c +218 -0
  77. data/ext/iodine/iodine_caller.h +27 -0
  78. data/ext/iodine/iodine_connection.c +933 -0
  79. data/ext/iodine/iodine_connection.h +55 -0
  80. data/ext/iodine/iodine_defer.c +420 -0
  81. data/ext/iodine/iodine_defer.h +6 -0
  82. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  83. data/ext/iodine/iodine_helpers.c +282 -0
  84. data/ext/iodine/iodine_helpers.h +12 -0
  85. data/ext/iodine/iodine_http.c +1171 -0
  86. data/ext/iodine/iodine_http.h +23 -0
  87. data/ext/iodine/iodine_json.c +302 -0
  88. data/ext/iodine/iodine_json.h +6 -0
  89. data/ext/iodine/iodine_mustache.c +567 -0
  90. data/ext/iodine/iodine_mustache.h +6 -0
  91. data/ext/iodine/iodine_pubsub.c +580 -0
  92. data/ext/iodine/iodine_pubsub.h +26 -0
  93. data/ext/iodine/iodine_rack_io.c +281 -0
  94. data/ext/iodine/iodine_rack_io.h +20 -0
  95. data/ext/iodine/iodine_store.c +142 -0
  96. data/ext/iodine/iodine_store.h +20 -0
  97. data/ext/iodine/iodine_tcp.c +346 -0
  98. data/ext/iodine/iodine_tcp.h +13 -0
  99. data/ext/iodine/iodine_tls.c +261 -0
  100. data/ext/iodine/iodine_tls.h +13 -0
  101. data/ext/iodine/mustache_parser.h +1546 -0
  102. data/ext/iodine/redis_engine.c +957 -0
  103. data/ext/iodine/redis_engine.h +79 -0
  104. data/ext/iodine/resp_parser.h +317 -0
  105. data/ext/iodine/websocket_parser.h +505 -0
  106. data/ext/iodine/websockets.c +735 -0
  107. data/ext/iodine/websockets.h +185 -0
  108. data/isomorfeus-iodine.gemspec +42 -0
  109. data/lib/iodine/connection.rb +61 -0
  110. data/lib/iodine/json.rb +42 -0
  111. data/lib/iodine/mustache.rb +113 -0
  112. data/lib/iodine/pubsub.rb +55 -0
  113. data/lib/iodine/rack_utils.rb +43 -0
  114. data/lib/iodine/tls.rb +16 -0
  115. data/lib/iodine/version.rb +3 -0
  116. data/lib/iodine.rb +274 -0
  117. data/lib/rack/handler/iodine.rb +33 -0
  118. data/logo.png +0 -0
  119. metadata +271 -0
@@ -0,0 +1,1430 @@
1
+ #include "iodine.h"
2
+
3
+ #include <ruby/version.h>
4
+
5
+ #define FIO_INCLUDE_LINKED_LIST
6
+ #include "fio.h"
7
+ #include "fio_cli.h"
8
+ /* *****************************************************************************
9
+ OS specific patches
10
+ ***************************************************************************** */
11
+
12
+ #ifdef __APPLE__
13
+ #include <dlfcn.h>
14
+ #endif
15
+
16
+ /** Any patches required by the running environment for consistent behavior */
17
+ static void patch_env(void) {
18
+ #ifdef __APPLE__
19
+ /* patch for dealing with the High Sierra `fork` limitations */
20
+ void *obj_c_runtime = dlopen("Foundation.framework/Foundation", RTLD_LAZY);
21
+ (void)obj_c_runtime;
22
+ #endif
23
+ }
24
+
25
+ /* *****************************************************************************
26
+ Constants and State
27
+ ***************************************************************************** */
28
+
29
+ VALUE IodineModule;
30
+ VALUE IodineBaseModule;
31
+
32
+ /** Default connection settings for {Iodine.listen} and {Iodine.connect}. */
33
+ VALUE iodine_default_args;
34
+
35
+ ID iodine_call_id;
36
+ ID iodine_to_s_id;
37
+
38
+ static VALUE address_sym;
39
+ static VALUE app_sym;
40
+ static VALUE body_sym;
41
+ static VALUE cookies_sym;
42
+ static VALUE handler_sym;
43
+ static VALUE headers_sym;
44
+ static VALUE log_sym;
45
+ static VALUE max_body_sym;
46
+ static VALUE max_clients_sym;
47
+ static VALUE max_headers_sym;
48
+ static VALUE max_msg_sym;
49
+ static VALUE method_sym;
50
+ static VALUE path_sym;
51
+ static VALUE ping_sym;
52
+ static VALUE port_sym;
53
+ static VALUE public_sym;
54
+ static VALUE service_sym;
55
+ static VALUE timeout_sym;
56
+ static VALUE tls_sym;
57
+ static VALUE url_sym;
58
+
59
+ /* *****************************************************************************
60
+ Idling
61
+ ***************************************************************************** */
62
+
63
+ /* performs a Ruby state callback and clears the Ruby object's memory */
64
+ static void iodine_perform_on_idle_callback(void *blk_) {
65
+ VALUE blk = (VALUE)blk_;
66
+ IodineCaller.call(blk, iodine_call_id);
67
+ IodineStore.remove(blk);
68
+ fio_state_callback_remove(FIO_CALL_ON_IDLE, iodine_perform_on_idle_callback,
69
+ blk_);
70
+ }
71
+
72
+ /**
73
+ Schedules a single occuring event for the next idle cycle.
74
+
75
+ To schedule a reoccuring event, reschedule the event at the end of it's
76
+ run.
77
+
78
+ i.e.
79
+
80
+ IDLE_PROC = Proc.new { puts "idle"; Iodine.on_idle &IDLE_PROC }
81
+ Iodine.on_idle &IDLE_PROC
82
+ */
83
+ static VALUE iodine_sched_on_idle(VALUE self) {
84
+ // clang-format on
85
+ rb_need_block();
86
+ VALUE block = rb_block_proc();
87
+ IodineStore.add(block);
88
+ fio_state_callback_add(FIO_CALL_ON_IDLE, iodine_perform_on_idle_callback,
89
+ (void *)block);
90
+ return block;
91
+ (void)self;
92
+ }
93
+
94
+ /* *****************************************************************************
95
+ Running Iodine
96
+ ***************************************************************************** */
97
+
98
+ typedef struct {
99
+ int16_t threads;
100
+ int16_t workers;
101
+ } iodine_start_params_s;
102
+
103
+ static void *iodine_run_outside_GVL(void *params_) {
104
+ iodine_start_params_s *params = params_;
105
+ fio_start(.threads = params->threads, .workers = params->workers);
106
+ return NULL;
107
+ }
108
+
109
+ /* *****************************************************************************
110
+ Core API
111
+ ***************************************************************************** */
112
+
113
+ /**
114
+ * Returns the number of worker threads that will be used when {Iodine.start}
115
+ * is called.
116
+ *
117
+ * Negative numbers are translated as fractions of the number of CPU cores.
118
+ * i.e., -2 == half the number of detected CPU cores.
119
+ *
120
+ * Zero values promise nothing (iodine will decide what to do with them).
121
+ *
122
+ * @return [FixNum] Thread Count
123
+ */
124
+ static VALUE iodine_threads_get(VALUE self) {
125
+ VALUE i = rb_ivar_get(self, rb_intern2("@threads", 8));
126
+ if (i == Qnil)
127
+ i = INT2NUM(0);
128
+ return i;
129
+ }
130
+
131
+ /**
132
+ * Sets the number of worker threads that will be used when {Iodine.start}
133
+ * is called.
134
+ *
135
+ * Negative numbers are translated as fractions of the number of CPU cores.
136
+ * i.e., -2 == half the number of detected CPU cores.
137
+ *
138
+ * Zero values promise nothing (iodine will decide what to do with them).
139
+ *
140
+ * @param thread_count [FixNum] The number of worker threads to use
141
+ */
142
+ static VALUE iodine_threads_set(VALUE self, VALUE val) {
143
+ Check_Type(val, T_FIXNUM);
144
+ if (NUM2SSIZET(val) >= (1 << 12)) {
145
+ rb_raise(rb_eRangeError, "requsted thread count is out of range.");
146
+ }
147
+ rb_ivar_set(self, rb_intern2("@threads", 8), val);
148
+ return val;
149
+ }
150
+
151
+ /**
152
+ * Gets the logging level used for Iodine messages.
153
+ *
154
+ * Levels range from 0-5, where:
155
+ *
156
+ * 0 == Quite (no messages)
157
+ * 1 == Fatal Errors only.
158
+ * 2 == Errors only (including fatal errors).
159
+ * 3 == Warnings and errors only.
160
+ * 4 == Informational messages, warnings and errors (default).
161
+ * 5 == Everything, including debug information.
162
+ *
163
+ * Logging is always performed to the process's STDERR and can be piped away.
164
+ *
165
+ * @return [FixNum] Logging Level
166
+ *
167
+ * NOTE: this does NOT effect HTTP logging.
168
+ */
169
+ static VALUE iodine_logging_get(VALUE self) {
170
+ return INT2FIX(FIO_LOG_LEVEL);
171
+ (void)self;
172
+ }
173
+
174
+ /**
175
+ * Gets the logging level used for Iodine messages.
176
+ *
177
+ * Levels range from 0-5, where:
178
+ *
179
+ * 0 == Quite (no messages)
180
+ * 1 == Fatal Errors only.
181
+ * 2 == Errors only (including fatal errors).
182
+ * 3 == Warnings and errors only.
183
+ * 4 == Informational messages, warnings and errors (default).
184
+ * 5 == Everything, including debug information.
185
+ *
186
+ * Logging is always performed to the process's STDERR and can be piped away.
187
+ *
188
+ * @param log_level [FixNum] Sets the logging level
189
+ *
190
+ * NOTE: this does NOT effect HTTP logging.
191
+ */
192
+ static VALUE iodine_logging_set(VALUE self, VALUE val) {
193
+ Check_Type(val, T_FIXNUM);
194
+ FIO_LOG_LEVEL = FIX2INT(val);
195
+ return self;
196
+ }
197
+
198
+ /**
199
+ * Returns the number of worker processes that will be used when {Iodine.start}
200
+ * is called.
201
+ *
202
+ * Negative numbers are translated as fractions of the number of CPU cores.
203
+ * i.e., -2 == half the number of detected CPU cores.
204
+ *
205
+ * Zero values promise nothing (iodine will decide what to do with them).
206
+ *
207
+ * 1 == single process mode, the msater process acts as a worker process.
208
+ *
209
+ * @return [FixNum] Worker Count
210
+ */
211
+ static VALUE iodine_workers_get(VALUE self) {
212
+ VALUE i = rb_ivar_get(self, rb_intern2("@workers", 8));
213
+ if (i == Qnil)
214
+ i = INT2NUM(0);
215
+ return i;
216
+ }
217
+
218
+ /**
219
+ * Sets the number of worker processes that will be used when {Iodine.start}
220
+ * is called.
221
+ *
222
+ * Negative numbers are translated as fractions of the number of CPU cores.
223
+ * i.e., -2 == half the number of detected CPU cores.
224
+ *
225
+ * Zero values promise nothing (iodine will decide what to do with them).
226
+ *
227
+ * 1 == single process mode, the msater process acts as a worker process.
228
+ *
229
+ * @param worker_count [FixNum] Number of worker processes
230
+ */
231
+ static VALUE iodine_workers_set(VALUE self, VALUE val) {
232
+ Check_Type(val, T_FIXNUM);
233
+ if (NUM2SSIZET(val) >= (1 << 9)) {
234
+ rb_raise(rb_eRangeError, "requsted worker process count is out of range.");
235
+ }
236
+ rb_ivar_set(self, rb_intern2("@workers", 8), val);
237
+ return val;
238
+ }
239
+
240
+ /** Logs the Iodine startup message */
241
+ static void iodine_print_startup_message(iodine_start_params_s params) {
242
+ VALUE iodine_version = rb_const_get(IodineModule, rb_intern("VERSION"));
243
+ VALUE ruby_version = rb_const_get(IodineModule, rb_intern("RUBY_VERSION"));
244
+ fio_expected_concurrency(&params.threads, &params.workers);
245
+ FIO_LOG_INFO("Starting up Iodine:\n"
246
+ " * Iodine %s\n * Ruby %s\n"
247
+ " * facil.io " FIO_VERSION_STRING " (%s)\n"
248
+ " * %d Workers X %d Threads per worker.\n"
249
+ " * Maximum %zu open files / sockets per worker.\n"
250
+ " * Master (root) process: %d.\n",
251
+ StringValueCStr(iodine_version), StringValueCStr(ruby_version),
252
+ fio_engine(), params.workers, params.threads, fio_capa(),
253
+ fio_parent_pid());
254
+ (void)params;
255
+ }
256
+
257
+ /**
258
+ * This will block the calling (main) thread and start the Iodine reactor.
259
+ *
260
+ * When using cluster mode (2 or more worker processes), it is important that no
261
+ * other threads are active.
262
+ *
263
+ * For many reasons, `fork` should NOT be called while multi-threading, so
264
+ * cluster mode must always be initiated from the main thread in a single thread
265
+ * environment.
266
+ *
267
+ * For information about why forking in multi-threaded environments should be
268
+ * avoided, see (for example):
269
+ * http://www.linuxprogrammingblog.com/threads-and-fork-think-twice-before-using-them
270
+ *
271
+ */
272
+ static VALUE iodine_start(VALUE self) {
273
+ if (fio_is_running()) {
274
+ rb_raise(rb_eRuntimeError, "Iodine already running!");
275
+ }
276
+ IodineCaller.set_GVL(1);
277
+ VALUE threads_rb = iodine_threads_get(self);
278
+ VALUE workers_rb = iodine_workers_get(self);
279
+ iodine_start_params_s params = {
280
+ .threads = NUM2SHORT(threads_rb),
281
+ .workers = NUM2SHORT(workers_rb),
282
+ };
283
+ iodine_print_startup_message(params);
284
+ IodineCaller.leaveGVL(iodine_run_outside_GVL, &params);
285
+ return self;
286
+ }
287
+
288
+ /**
289
+ * This will stop the iodine server, shutting it down.
290
+ *
291
+ * If called within a worker process (rather than the root/master process), this
292
+ * will cause a hot-restart for the worker.
293
+ */
294
+ static VALUE iodine_stop(VALUE self) {
295
+ fio_stop();
296
+ return self;
297
+ }
298
+
299
+ /**
300
+ * Returns `true` if this process is the master / root process, `false`
301
+ * otherwise.
302
+ *
303
+ * Note that the master process might be a worker process as well, when running
304
+ * in single process mode (see {Iodine.workers}).
305
+ */
306
+ static VALUE iodine_master_is(VALUE self) {
307
+ return fio_is_master() ? Qtrue : Qfalse;
308
+ }
309
+
310
+ /**
311
+ * Returns `true` if this process is a worker process or if iodine is running in
312
+ * a single process mode (the master is also a worker), `false` otherwise.
313
+ */
314
+ static VALUE iodine_worker_is(VALUE self) {
315
+ return fio_is_master() ? Qtrue : Qfalse;
316
+ }
317
+
318
+ /**
319
+ * Returns `true` if Iodine is currently running a server
320
+ */
321
+ static VALUE iodine_running(VALUE self) {
322
+ if (fio_is_running()) {
323
+ return Qtrue;
324
+ } else {
325
+ return Qfalse;
326
+ }
327
+ }
328
+
329
+ /* *****************************************************************************
330
+ CLI parser (Ruby's OptParser is more limiting than I knew...)
331
+ ***************************************************************************** */
332
+
333
+ /**
334
+ * Parses the CLI argnumnents, returning the Rack filename (if provided).
335
+ *
336
+ * Unknown arguments are ignored.
337
+ *
338
+ * @params [String] desc a String containg the iodine server's description.
339
+ */
340
+ static VALUE iodine_cli_parse(VALUE self) {
341
+ (void)self;
342
+ VALUE ARGV = rb_get_argv();
343
+ VALUE ret = Qtrue;
344
+ VALUE defaults = iodine_default_args;
345
+ VALUE iodine_version = rb_const_get(IodineModule, rb_intern("VERSION"));
346
+ char desc[1024];
347
+ if (!defaults || !ARGV || TYPE(ARGV) != T_ARRAY || TYPE(defaults) != T_HASH ||
348
+ TYPE(iodine_version) != T_STRING || RSTRING_LEN(iodine_version) > 512) {
349
+ FIO_LOG_ERROR("CLI parsing initialization error "
350
+ "ARGV=%p, Array?(%d), defaults == %p (%d)",
351
+ (void *)ARGV, (int)(TYPE(ARGV) == T_ARRAY), (void *)defaults,
352
+ (int)(TYPE(defaults) == T_HASH));
353
+ return Qnil;
354
+ }
355
+ /* Copy the Ruby ARGV to a C valid ARGV */
356
+ int argc = (int)RARRAY_LEN(ARGV) + 1;
357
+ if (argc <= 1) {
358
+ FIO_LOG_DEBUG("CLI: No arguments to parse...\n");
359
+ return Qnil;
360
+ } else {
361
+ FIO_LOG_DEBUG("Iodine CLI parsing %d arguments", argc);
362
+ }
363
+ char **argv = calloc(argc, sizeof(*argv));
364
+ FIO_ASSERT_ALLOC(argv);
365
+ argv[0] = (char *)"iodine";
366
+ for (int i = 1; i < argc; ++i) {
367
+ VALUE tmp = rb_ary_entry(ARGV, (long)(i - 1));
368
+ if (TYPE(tmp) != T_STRING) {
369
+ FIO_LOG_ERROR("ARGV Array contains a non-String object.");
370
+ ret = Qnil;
371
+ goto finish;
372
+ }
373
+ fio_str_info_s s = IODINE_RSTRINFO(tmp);
374
+ argv[i] = malloc(s.len + 1);
375
+ FIO_ASSERT_ALLOC(argv[i]);
376
+ memcpy(argv[i], s.data, s.len);
377
+ argv[i][s.len] = 0;
378
+ }
379
+ /* Levarage the facil.io CLI library */
380
+ memcpy(desc, "Iodine's HTTP/WebSocket server version ", 39);
381
+ memcpy(desc + 39, StringValueCStr(iodine_version),
382
+ RSTRING_LEN(iodine_version));
383
+ memcpy(desc + 39 + RSTRING_LEN(iodine_version),
384
+ "\r\n\r\nUse:\r\n iodine <options> <filename>\r\n\r\n"
385
+ "Both <options> and <filename> are optional. i.e.,:\r\n"
386
+ " iodine -p 0 -b /tmp/my_unix_sock\r\n"
387
+ " iodine -p 8080 path/to/app/conf.ru\r\n"
388
+ " iodine -p 8080 -w 4 -t 16\r\n"
389
+ " iodine -w -1 -t 4 -r redis://usr:pass@localhost:6379/",
390
+ 263);
391
+ desc[39 + 263 + RSTRING_LEN(iodine_version)] = 0;
392
+ fio_cli_start(
393
+ argc, (const char **)argv, 0, -1, desc,
394
+ FIO_CLI_PRINT_HEADER("Address Binding:"),
395
+ "-bind -b -address address to listen to. defaults to any available.",
396
+ FIO_CLI_INT("-port -p port number to listen to. defaults port 3000"),
397
+ FIO_CLI_PRINT("\t\t\x1B[4mNote\x1B[0m: to bind to a Unix socket, set "
398
+ "\x1B[1mport\x1B[0m to 0."),
399
+ FIO_CLI_PRINT_HEADER("Concurrency:"),
400
+ FIO_CLI_INT("-threads -t number of threads per process."),
401
+ FIO_CLI_INT("-workers -w number of processes to use."),
402
+ FIO_CLI_PRINT("Negative concurrency values "
403
+ "map to fractions of available CPU cores."),
404
+ FIO_CLI_PRINT_HEADER("HTTP Settings:"),
405
+ FIO_CLI_STRING("-public -www public folder, for static file service."),
406
+ FIO_CLI_INT("-keep-alive -k -tout HTTP keep-alive timeout in seconds "
407
+ "(0..255). Default: 40s"),
408
+ FIO_CLI_BOOL("-log -v HTTP request logging."),
409
+ FIO_CLI_INT(
410
+ "-max-body -maxbd HTTP upload limit in Mega-Bytes. Default: 50Mb"),
411
+ FIO_CLI_INT("-max-header -maxhd header limit per HTTP request in Kb. "
412
+ "Default: 32Kb."),
413
+ FIO_CLI_PRINT_HEADER("WebSocket Settings:"),
414
+ FIO_CLI_INT("-max-msg -maxms incoming WebSocket message limit in Kb. "
415
+ "Default: 250Kb"),
416
+ FIO_CLI_INT("-ping websocket ping interval (1..255). Default: 40s"),
417
+ FIO_CLI_PRINT_HEADER("SSL/TLS:"),
418
+ FIO_CLI_BOOL("-tls enable SSL/TLS using a self-signed certificate."),
419
+ FIO_CLI_STRING(
420
+ "-tls-cert -cert the SSL/TLS public certificate file name."),
421
+ FIO_CLI_STRING("-tls-key -key the SSL/TLS private key file name."),
422
+ FIO_CLI_STRING(
423
+ "-tls-pass -tls-password the password (if any) protecting the "
424
+ "private key file."),
425
+ FIO_CLI_PRINT("\t\t\x1B[1m-tls-password\x1B[0m is deprecated, use "
426
+ "\x1B[1m-tls-pass\x1B[0m"),
427
+ FIO_CLI_PRINT_HEADER("Connecting Iodine to Redis:"),
428
+ FIO_CLI_STRING(
429
+ "-redis -r an optional Redis URL server address. Default: none."),
430
+ FIO_CLI_INT(
431
+ "-redis-ping -rp websocket ping interval (0..255). Default: 300s"),
432
+ FIO_CLI_PRINT_HEADER("Misc:"),
433
+ FIO_CLI_STRING("-config -C configuration file to be loaded."),
434
+ FIO_CLI_STRING("-pid -pidfile name for the pid file to be created."),
435
+ FIO_CLI_INT("-verbosity -V 0..5 server verbosity level. Default: 4"),
436
+ FIO_CLI_BOOL(
437
+ "-warmup --preload warm up the application. CAREFUL! with workers."));
438
+
439
+ /* copy values from CLI library to iodine */
440
+ if (fio_cli_get("-V")) {
441
+ int level = fio_cli_get_i("-V");
442
+ if (level > 0 && level < 100)
443
+ FIO_LOG_LEVEL = level;
444
+ }
445
+ if (!fio_cli_get("-w") && getenv("WEB_CONCURRENCY")) {
446
+ fio_cli_set("-w", getenv("WEB_CONCURRENCY"));
447
+ }
448
+ if (!fio_cli_get("-w") && getenv("WORKERS")) {
449
+ fio_cli_set("-w", getenv("WORKERS"));
450
+ }
451
+ if (fio_cli_get("-w")) {
452
+ iodine_workers_set(IodineModule, INT2NUM(fio_cli_get_i("-w")));
453
+ }
454
+ if (!fio_cli_get("-t") && getenv("THREADS")) {
455
+ fio_cli_set("-t", getenv("THREADS"));
456
+ }
457
+ if (fio_cli_get("-t")) {
458
+ iodine_threads_set(IodineModule, INT2NUM(fio_cli_get_i("-t")));
459
+ }
460
+ if (fio_cli_get_bool("-v")) {
461
+ rb_hash_aset(defaults, log_sym, Qtrue);
462
+ }
463
+ if (fio_cli_get_bool("-warmup")) {
464
+ rb_hash_aset(defaults, ID2SYM(rb_intern("warmup_")), Qtrue);
465
+ }
466
+ // if (!fio_cli_get("-b") && getenv("ADDRESS")) {
467
+ // fio_cli_set("-b", getenv("ADDRESS"));
468
+ // }
469
+ if (fio_cli_get("-b")) {
470
+ if (fio_cli_get("-b")[0] == '/' ||
471
+ (fio_cli_get("-b")[0] == '.' && fio_cli_get("-b")[1] == '/')) {
472
+ if (fio_cli_get("-p") &&
473
+ (fio_cli_get("-p")[0] != '0' || fio_cli_get("-p")[1])) {
474
+ FIO_LOG_WARNING(
475
+ "Detected a Unix socket binding (-b) conflicting with port.\n"
476
+ " Port settings (-p %s) are ignored",
477
+ fio_cli_get("-p"));
478
+ }
479
+ fio_cli_set("-p", "0");
480
+ } else {
481
+ // if (!fio_cli_get("-p") && getenv("PORT")) {
482
+ // fio_cli_set("-p", getenv("PORT"));
483
+ // }
484
+ }
485
+ rb_hash_aset(defaults, address_sym, rb_str_new_cstr(fio_cli_get("-b")));
486
+ }
487
+ if (fio_cli_get("-p")) {
488
+ rb_hash_aset(defaults, port_sym, rb_str_new_cstr(fio_cli_get("-p")));
489
+ }
490
+ if (fio_cli_get("-www")) {
491
+ rb_hash_aset(defaults, public_sym, rb_str_new_cstr(fio_cli_get("-www")));
492
+ }
493
+ if (!fio_cli_get("-redis") && getenv("IODINE_REDIS_URL")) {
494
+ fio_cli_set("-redis", getenv("IODINE_REDIS_URL"));
495
+ }
496
+ if (fio_cli_get("-redis")) {
497
+ rb_hash_aset(defaults, ID2SYM(rb_intern("redis_")),
498
+ rb_str_new_cstr(fio_cli_get("-redis")));
499
+ }
500
+ if (fio_cli_get("-k")) {
501
+ rb_hash_aset(defaults, timeout_sym, INT2NUM(fio_cli_get_i("-k")));
502
+ }
503
+ if (fio_cli_get("-ping")) {
504
+ rb_hash_aset(defaults, ping_sym, INT2NUM(fio_cli_get_i("-ping")));
505
+ }
506
+ if (fio_cli_get("-redis-ping")) {
507
+ rb_hash_aset(defaults, ID2SYM(rb_intern("redis_ping_")),
508
+ INT2NUM(fio_cli_get_i("-redis-ping")));
509
+ }
510
+ if (fio_cli_get("-max-body")) {
511
+ rb_hash_aset(defaults, max_body_sym,
512
+ INT2NUM((fio_cli_get_i("-max-body") /* * 1024 * 1024 */)));
513
+ }
514
+ if (fio_cli_get("-maxms")) {
515
+ rb_hash_aset(defaults, max_msg_sym,
516
+ INT2NUM((fio_cli_get_i("-maxms") /* * 1024 */)));
517
+ }
518
+ if (fio_cli_get("-maxhd")) {
519
+ rb_hash_aset(defaults, max_headers_sym,
520
+ INT2NUM((fio_cli_get_i("-maxhd") /* * 1024 */)));
521
+ }
522
+ #ifndef __MINGW32__
523
+ if (fio_cli_get_bool("-tls") || fio_cli_get("-key") || fio_cli_get("-cert")) {
524
+ VALUE rbtls = IodineCaller.call(IodineTLSClass, rb_intern2("new", 3));
525
+ if (rbtls == Qnil) {
526
+ FIO_LOG_FATAL("Iodine internal error, Ruby TLS object is nil.");
527
+ exit(-1);
528
+ }
529
+ fio_tls_s *tls = iodine_tls2c(rbtls);
530
+ if (!tls) {
531
+ FIO_LOG_FATAL("Iodine internal error, TLS object NULL.");
532
+ exit(-1);
533
+ }
534
+ if (fio_cli_get("-tls-key") && fio_cli_get("-tls-cert")) {
535
+ fio_tls_cert_add(tls, NULL, fio_cli_get("-tls-cert"),
536
+ fio_cli_get("-tls-key"), fio_cli_get("-tls-pass"));
537
+ } else {
538
+ if (!fio_cli_get_bool("-tls"))
539
+ FIO_LOG_ERROR("TLS support requires both key and certificate."
540
+ "\r\n\t\tfalling back on a self signed certificate.");
541
+ char name[1024];
542
+ fio_local_addr(name, 1024);
543
+ fio_tls_cert_add(tls, name, NULL, NULL, NULL);
544
+ }
545
+ rb_hash_aset(defaults, tls_sym, rbtls);
546
+ }
547
+ #endif
548
+ if (fio_cli_unnamed_count()) {
549
+ rb_hash_aset(defaults, ID2SYM(rb_intern("filename_")),
550
+ rb_str_new_cstr(fio_cli_unnamed(0)));
551
+ }
552
+ if (fio_cli_get("-pid")) {
553
+ VALUE pid_filename = rb_str_new_cstr(fio_cli_get("-pid"));
554
+ rb_hash_aset(defaults, ID2SYM(rb_intern("pid_")), pid_filename);
555
+ rb_hash_aset(defaults, ID2SYM(rb_intern("pid")), pid_filename);
556
+ }
557
+ if (fio_cli_get("-config")) {
558
+ VALUE conf_filename = rb_str_new_cstr(fio_cli_get("-config"));
559
+ rb_hash_aset(defaults, ID2SYM(rb_intern("conf_")), conf_filename);
560
+ }
561
+
562
+ /* create `filename` String, cleanup and return */
563
+ fio_cli_end();
564
+ finish:
565
+ for (int i = 1; i < argc; ++i) {
566
+ free(argv[i]);
567
+ }
568
+ free(argv);
569
+ return ret;
570
+ }
571
+
572
+ /* *****************************************************************************
573
+ Argument support for `connect` / `listen`
574
+ ***************************************************************************** */
575
+
576
+ static int for_each_header_value(VALUE key, VALUE val, VALUE h_) {
577
+ FIOBJ h = h_;
578
+ if (RB_TYPE_P(key, T_SYMBOL))
579
+ key = rb_sym2str(key);
580
+ if (!RB_TYPE_P(key, T_STRING)) {
581
+ FIO_LOG_WARNING("invalid key type in header hash, ignored.");
582
+ return ST_CONTINUE;
583
+ }
584
+ if (RB_TYPE_P(val, T_SYMBOL))
585
+ val = rb_sym2str(val);
586
+ if (RB_TYPE_P(val, T_STRING)) {
587
+ FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key));
588
+ fiobj_hash_set(h, k, fiobj_str_new(RSTRING_PTR(val), RSTRING_LEN(val)));
589
+ fiobj_free(k);
590
+ } else if (RB_TYPE_P(val, T_ARRAY)) {
591
+ FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key));
592
+ size_t len = rb_array_len(val);
593
+ FIOBJ v = fiobj_ary_new2(len);
594
+ fiobj_hash_set(h, k, v);
595
+ fiobj_free(k);
596
+ for (size_t i = 0; i < len; ++i) {
597
+ VALUE tmp = rb_ary_entry(val, i);
598
+ if (RB_TYPE_P(tmp, T_SYMBOL))
599
+ tmp = rb_sym2str(tmp);
600
+ if (RB_TYPE_P(tmp, T_STRING))
601
+ fiobj_ary_push(v, fiobj_str_new(RSTRING_PTR(tmp), RSTRING_LEN(tmp)));
602
+ }
603
+ } else {
604
+ FIO_LOG_WARNING("invalid header value type, ignored.");
605
+ }
606
+ return ST_CONTINUE;
607
+ }
608
+
609
+ static int for_each_cookie(VALUE key, VALUE val, VALUE h_) {
610
+ FIOBJ h = h_;
611
+ if (RB_TYPE_P(key, T_SYMBOL))
612
+ key = rb_sym2str(key);
613
+ if (!RB_TYPE_P(key, T_STRING)) {
614
+ FIO_LOG_WARNING("invalid key type in cookie hash, ignored.");
615
+ return ST_CONTINUE;
616
+ }
617
+ if (RB_TYPE_P(val, T_SYMBOL))
618
+ val = rb_sym2str(val);
619
+ if (RB_TYPE_P(val, T_STRING)) {
620
+ FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key));
621
+ fiobj_hash_set(h, k, fiobj_str_new(RSTRING_PTR(val), RSTRING_LEN(val)));
622
+ fiobj_free(k);
623
+ } else {
624
+ FIO_LOG_WARNING("invalid cookie value type, ignored.");
625
+ }
626
+ return ST_CONTINUE;
627
+ }
628
+
629
+ /* cleans up any resources used by the argument list processing */
630
+ FIO_FUNC void iodine_connect_args_cleanup(iodine_connection_args_s *s) {
631
+ if (!s)
632
+ return;
633
+ fiobj_free(s->cookies);
634
+ fiobj_free(s->headers);
635
+ if (s->port.capa)
636
+ fio_free(s->port.data);
637
+ if (s->address.capa)
638
+ fio_free(s->address.data);
639
+ #ifndef __MINGW32__
640
+ if (s->tls)
641
+ fio_tls_destroy(s->tls);
642
+ #endif
643
+ }
644
+
645
+ /*
646
+ Accepts:
647
+
648
+ func(settings)
649
+
650
+ Supported Settigs:
651
+ - `:url`
652
+ - `:handler` (deprecated: `app`)
653
+ - `:service` (raw / ws / wss / http / https )
654
+ - `:address`
655
+ - `:port`
656
+ - `:path` (HTTP/WebSocket client)
657
+ - `:method` (HTTP client)
658
+ - `:headers` (HTTP/WebSocket client)
659
+ - `:cookies` (HTTP/WebSocket client)
660
+ - `:body` (HTTP client)
661
+ - `:tls`
662
+ - `:log` (HTTP only)
663
+ - `:public` (public folder, HTTP server only)
664
+ - `:timeout` (HTTP only)
665
+ - `:ping` (`:raw` clients and WebSockets only)
666
+ - `:max_headers` (HTTP only)
667
+ - `:max_body` (HTTP only)
668
+ - `:max_msg` (WebSockets only)
669
+
670
+ */
671
+ FIO_FUNC iodine_connection_args_s iodine_connect_args(VALUE s, uint8_t is_srv) {
672
+ Check_Type(s, T_HASH);
673
+ iodine_connection_args_s r = {.ping = 0}; /* set all to 0 */
674
+ /* Collect argument values */
675
+ VALUE address = rb_hash_aref(s, address_sym);
676
+ VALUE app = rb_hash_aref(s, app_sym);
677
+ VALUE body = rb_hash_aref(s, body_sym);
678
+ VALUE cookies = rb_hash_aref(s, cookies_sym);
679
+ VALUE handler = rb_hash_aref(s, handler_sym);
680
+ VALUE headers = rb_hash_aref(s, headers_sym);
681
+ VALUE log = rb_hash_aref(s, log_sym);
682
+ VALUE max_body = rb_hash_aref(s, max_body_sym);
683
+ VALUE max_clients = rb_hash_aref(s, max_clients_sym);
684
+ VALUE max_headers = rb_hash_aref(s, max_headers_sym);
685
+ VALUE max_msg = rb_hash_aref(s, max_msg_sym);
686
+ VALUE method = rb_hash_aref(s, method_sym);
687
+ VALUE path = rb_hash_aref(s, path_sym);
688
+ VALUE ping = rb_hash_aref(s, ping_sym);
689
+ VALUE port = rb_hash_aref(s, port_sym);
690
+ VALUE r_public = rb_hash_aref(s, public_sym);
691
+ VALUE service = rb_hash_aref(s, service_sym);
692
+ VALUE timeout = rb_hash_aref(s, timeout_sym);
693
+ #ifndef __MINGW32__
694
+ VALUE tls = rb_hash_aref(s, tls_sym);
695
+ #endif
696
+ VALUE r_url = rb_hash_aref(s, url_sym);
697
+ fio_str_info_s service_str = {.data = NULL};
698
+
699
+ /* Complete using default values */
700
+ if (address == Qnil)
701
+ address = rb_hash_aref(iodine_default_args, address_sym);
702
+ if (app == Qnil)
703
+ app = rb_hash_aref(iodine_default_args, app_sym);
704
+ if (cookies == Qnil)
705
+ cookies = rb_hash_aref(iodine_default_args, cookies_sym);
706
+ if (handler == Qnil)
707
+ handler = rb_hash_aref(iodine_default_args, handler_sym);
708
+ if (headers == Qnil)
709
+ headers = rb_hash_aref(iodine_default_args, headers_sym);
710
+ if (log == Qnil)
711
+ log = rb_hash_aref(iodine_default_args, log_sym);
712
+ if (max_body == Qnil)
713
+ max_body = rb_hash_aref(iodine_default_args, max_body_sym);
714
+ if (max_clients == Qnil)
715
+ max_clients = rb_hash_aref(iodine_default_args, max_clients_sym);
716
+ if (max_headers == Qnil)
717
+ max_headers = rb_hash_aref(iodine_default_args, max_headers_sym);
718
+ if (max_msg == Qnil)
719
+ max_msg = rb_hash_aref(iodine_default_args, max_msg_sym);
720
+ if (method == Qnil)
721
+ method = rb_hash_aref(iodine_default_args, method_sym);
722
+ if (path == Qnil)
723
+ path = rb_hash_aref(iodine_default_args, path_sym);
724
+ if (ping == Qnil)
725
+ ping = rb_hash_aref(iodine_default_args, ping_sym);
726
+ if (port == Qnil)
727
+ port = rb_hash_aref(iodine_default_args, port_sym);
728
+ if (r_public == Qnil) {
729
+ r_public = rb_hash_aref(iodine_default_args, public_sym);
730
+ }
731
+ // if (service == Qnil) // not supported by default settings...
732
+ // service = rb_hash_aref(iodine_default_args, service_sym);
733
+ if (timeout == Qnil)
734
+ timeout = rb_hash_aref(iodine_default_args, timeout_sym);
735
+ #ifndef __MINGW32__
736
+ if (tls == Qnil)
737
+ tls = rb_hash_aref(iodine_default_args, tls_sym);
738
+ #endif
739
+
740
+ /* TODO: deprecation */
741
+ if (handler == Qnil) {
742
+ handler = rb_hash_aref(s, app_sym);
743
+ if (handler != Qnil)
744
+ FIO_LOG_WARNING(":app is deprecated in Iodine.listen and Iodine.connect. "
745
+ "Use :handler");
746
+ }
747
+
748
+ /* specific for HTTP */
749
+ if (is_srv && handler == Qnil && rb_block_given_p()) {
750
+ handler = rb_block_proc();
751
+ }
752
+
753
+ /* Raise exceptions on errors (last chance) */
754
+ if (handler == Qnil) {
755
+ rb_raise(rb_eArgError, "a :handler is required.");
756
+ }
757
+
758
+ /* Set existing values */
759
+ if (handler != Qnil) {
760
+ r.handler = handler;
761
+ }
762
+ if (address != Qnil && RB_TYPE_P(address, T_STRING)) {
763
+ r.address = IODINE_RSTRINFO(address);
764
+ }
765
+ if (body != Qnil && RB_TYPE_P(body, T_STRING)) {
766
+ r.body = IODINE_RSTRINFO(body);
767
+ }
768
+ if (cookies != Qnil && RB_TYPE_P(cookies, T_HASH)) {
769
+ r.cookies = fiobj_hash_new2(rb_hash_size(cookies));
770
+ rb_hash_foreach(cookies, for_each_cookie, r.cookies);
771
+ }
772
+ if (headers != Qnil && RB_TYPE_P(headers, T_HASH)) {
773
+ r.headers = fiobj_hash_new2(rb_hash_size(headers));
774
+ rb_hash_foreach(headers, for_each_header_value, r.headers);
775
+ }
776
+ if (log != Qnil && log != Qfalse) {
777
+ r.log = 1;
778
+ }
779
+ if (max_body != Qnil && RB_TYPE_P(max_body, T_FIXNUM)) {
780
+ r.max_body = FIX2ULONG(max_body) * 1024 * 1024;
781
+ }
782
+ if (max_clients != Qnil && RB_TYPE_P(max_clients, T_FIXNUM)) {
783
+ r.max_clients = FIX2ULONG(max_clients);
784
+ }
785
+ if (max_headers != Qnil && RB_TYPE_P(max_headers, T_FIXNUM)) {
786
+ r.max_headers = FIX2ULONG(max_headers) * 1024;
787
+ }
788
+ if (max_msg != Qnil && RB_TYPE_P(max_msg, T_FIXNUM)) {
789
+ r.max_msg = FIX2ULONG(max_msg) * 1024;
790
+ }
791
+ if (method != Qnil && RB_TYPE_P(method, T_STRING)) {
792
+ r.method = IODINE_RSTRINFO(method);
793
+ }
794
+ if (path != Qnil && RB_TYPE_P(path, T_STRING)) {
795
+ r.path = IODINE_RSTRINFO(path);
796
+ }
797
+ if (ping != Qnil && RB_TYPE_P(ping, T_FIXNUM)) {
798
+ if (FIX2ULONG(ping) > 255)
799
+ FIO_LOG_WARNING(":ping value over 255 will be silently ignored.");
800
+ else
801
+ r.ping = FIX2ULONG(ping);
802
+ }
803
+ if (port != Qnil) {
804
+ if (RB_TYPE_P(port, T_STRING)) {
805
+ char *tmp = RSTRING_PTR(port);
806
+ if (fio_atol(&tmp))
807
+ r.port = IODINE_RSTRINFO(port);
808
+ } else if (RB_TYPE_P(port, T_FIXNUM) && FIX2UINT(port)) {
809
+ if (FIX2UINT(port) >= 65536) {
810
+ FIO_LOG_WARNING("Port number %u is too high, quietly ignored.",
811
+ FIX2UINT(port));
812
+ } else {
813
+ r.port = (fio_str_info_s){.data = fio_malloc(16), .len = 0, .capa = 1};
814
+ r.port.len = fio_ltoa(r.port.data, FIX2INT(port), 10);
815
+ r.port.data[r.port.len] = 0;
816
+ }
817
+ }
818
+ }
819
+
820
+ if (r_public != Qnil && RB_TYPE_P(r_public, T_STRING)) {
821
+ r.public = IODINE_RSTRINFO(r_public);
822
+ }
823
+ if (service != Qnil && RB_TYPE_P(service, T_STRING)) {
824
+ service_str = IODINE_RSTRINFO(service);
825
+ } else if (service != Qnil && RB_TYPE_P(service, T_SYMBOL)) {
826
+ service = rb_sym2str(service);
827
+ service_str = IODINE_RSTRINFO(service);
828
+ }
829
+ if (timeout != Qnil && RB_TYPE_P(ping, T_FIXNUM)) {
830
+ if (FIX2ULONG(timeout) > 255)
831
+ FIO_LOG_WARNING(":timeout value over 255 will be silently ignored.");
832
+ else
833
+ r.timeout = FIX2ULONG(timeout);
834
+ }
835
+ #ifndef __MINGW32__
836
+ if (tls != Qnil) {
837
+ r.tls = iodine_tls2c(tls);
838
+ if (r.tls)
839
+ fio_tls_dup(r.tls);
840
+ }
841
+ #endif
842
+ /* URL parsing */
843
+ if (r_url != Qnil && RB_TYPE_P(r_url, T_STRING)) {
844
+ r.url = IODINE_RSTRINFO(r_url);
845
+ fio_url_s u = fio_url_parse(r.url.data, r.url.len);
846
+ /* set service string */
847
+ if (u.scheme.data) {
848
+ service_str = u.scheme;
849
+ }
850
+ /* copy port number */
851
+ if (u.port.data) {
852
+ char *tmp = u.port.data;
853
+ if (fio_atol(&tmp) == 0) {
854
+ if (r.port.capa)
855
+ fio_free(r.port.data);
856
+ r.port = (fio_str_info_s){.data = NULL};
857
+ } else {
858
+ if (u.port.len > 5)
859
+ FIO_LOG_WARNING("Port number error (%.*s too long to be valid).",
860
+ (int)u.port.len, u.port.data);
861
+ if (r.port.capa && u.port.len >= 16) {
862
+ fio_free(r.port.data);
863
+ r.port = (fio_str_info_s){.data = NULL};
864
+ }
865
+ if (!r.port.capa)
866
+ r.port = (fio_str_info_s){
867
+ .data = fio_malloc(u.port.len + 1), .len = u.port.len, .capa = 1};
868
+ memcpy(r.port.data, u.port.data, u.port.len);
869
+ r.port.len = u.port.len;
870
+ r.port.data[r.port.len] = 0;
871
+ }
872
+ } else {
873
+ if (r.port.capa)
874
+ fio_free(r.port.data);
875
+ r.port = (fio_str_info_s){.data = NULL};
876
+ }
877
+ /* copy host / address */
878
+ if (u.host.data) {
879
+ r.address = (fio_str_info_s){
880
+ .data = fio_malloc(u.host.len + 1), .len = u.host.len, .capa = 1};
881
+ memcpy(r.address.data, u.host.data, u.host.len);
882
+ r.address.len = u.host.len;
883
+ r.address.data[r.address.len] = 0;
884
+ } else {
885
+ if (r.address.capa)
886
+ fio_free(r.address.data);
887
+ r.address = (fio_str_info_s){.data = NULL};
888
+ }
889
+ /* set path */
890
+ if (u.path.data) {
891
+ /* support possible Unix address as "raw://:0/my/sock.sock" */
892
+ if (r.address.data || r.port.data)
893
+ r.path = u.path;
894
+ else
895
+ r.address = u.path;
896
+ }
897
+ }
898
+ /* test/set service type */
899
+ r.service = IODINE_SERVICE_RAW;
900
+ if (service_str.data) {
901
+ #ifdef __MINGW32__
902
+ switch (service_str.data[0]) {
903
+ case 't': /* overflow */
904
+ /* tcp or tls */
905
+ if (service_str.data[1] == 'l') {
906
+ char *local = NULL;
907
+ char buf[1024];
908
+ buf[1023] = 0;
909
+ if (is_srv) {
910
+ local = buf;
911
+ if (fio_local_addr(buf, 1023) >= 1022)
912
+ local = NULL;
913
+ }
914
+ }
915
+ /* overflow */
916
+ case 'u': /* overflow */
917
+ /* unix */
918
+ case 'r':
919
+ /* raw */
920
+ r.service = IODINE_SERVICE_RAW;
921
+ break;
922
+ case 'h':
923
+ /* http(s) */
924
+ r.service = IODINE_SERVICE_HTTP;
925
+ if (service_str.len == 5) {
926
+ char *local = NULL;
927
+ char buf[1024];
928
+ buf[1023] = 0;
929
+ if (is_srv) {
930
+ local = buf;
931
+ if (fio_local_addr(buf, 1023) >= 1022)
932
+ local = NULL;
933
+ }
934
+ }
935
+ case 'w':
936
+ /* ws(s) */
937
+ r.service = IODINE_SERVICE_WS;
938
+ if (service_str.len == 3) {
939
+ char *local = NULL;
940
+ char buf[1024];
941
+ buf[1023] = 0;
942
+ if (is_srv) {
943
+ local = buf;
944
+ if (fio_local_addr(buf, 1023) >= 1022)
945
+ local = NULL;
946
+ }
947
+ }
948
+ break;
949
+ }
950
+ #else
951
+ switch (service_str.data[0]) {
952
+ case 't': /* overflow */
953
+ /* tcp or tls */
954
+ if (service_str.data[1] == 'l' && !r.tls) {
955
+ char *local = NULL;
956
+ char buf[1024];
957
+ buf[1023] = 0;
958
+ if (is_srv) {
959
+ local = buf;
960
+ if (fio_local_addr(buf, 1023) >= 1022)
961
+ local = NULL;
962
+ }
963
+ r.tls = fio_tls_new(local, NULL, NULL, NULL);
964
+ }
965
+ /* overflow */
966
+ case 'u': /* overflow */
967
+ /* unix */
968
+ case 'r':
969
+ /* raw */
970
+ r.service = IODINE_SERVICE_RAW;
971
+ break;
972
+ case 'h':
973
+ /* http(s) */
974
+ r.service = IODINE_SERVICE_HTTP;
975
+ if (service_str.len == 5 && !r.tls) {
976
+ char *local = NULL;
977
+ char buf[1024];
978
+ buf[1023] = 0;
979
+ if (is_srv) {
980
+ local = buf;
981
+ if (fio_local_addr(buf, 1023) >= 1022)
982
+ local = NULL;
983
+ }
984
+ r.tls = fio_tls_new(local, NULL, NULL, NULL);
985
+ }
986
+ case 'w':
987
+ /* ws(s) */
988
+ r.service = IODINE_SERVICE_WS;
989
+ if (service_str.len == 3 && !r.tls) {
990
+ char *local = NULL;
991
+ char buf[1024];
992
+ buf[1023] = 0;
993
+ if (is_srv) {
994
+ local = buf;
995
+ if (fio_local_addr(buf, 1023) >= 1022)
996
+ local = NULL;
997
+ }
998
+ r.tls = fio_tls_new(local, NULL, NULL, NULL);
999
+ }
1000
+ break;
1001
+ }
1002
+ #endif
1003
+ }
1004
+ return r;
1005
+ }
1006
+
1007
+ /* *****************************************************************************
1008
+ Listen function routing
1009
+ ***************************************************************************** */
1010
+
1011
+ // clang-format off
1012
+ /*
1013
+ {Iodine.listen} can be used to listen to any incoming connections, including HTTP and raw (tcp/ip and unix sockets) connections.
1014
+
1015
+ Iodine.listen(settings)
1016
+
1017
+ Supported Settigs:
1018
+
1019
+ | | |
1020
+ |---|---|
1021
+ | `:url` | URL indicating service type, host name and port. Path will be parsed as a Unix socket. |
1022
+ | `:handler` | (deprecated: `:app`) see details below. |
1023
+ | `:address` | an IP address or a unix socket address. Only relevant if `:url` is missing. |
1024
+ | `:log` | (HTTP only) request logging. For global verbosity see {Iodine.verbosity} |
1025
+ | `:max_body` | (HTTP only) maximum upload size allowed per request before disconnection (in Mb). |
1026
+ | `:max_headers` | (HTTP only) maximum total header length allowed per request (in Kb). |
1027
+ | `:max_msg` | (WebSockets only) maximum message size pre message (in Kb). |
1028
+ | `:ping` | (`:raw` clients and WebSockets only) ping interval (in seconds). Up to 255 seconds. |
1029
+ | `:port` | port number to listen to either a String or Number) |
1030
+ | `:public` | (HTTP server only) public folder for static file service. |
1031
+ | `:service` | (`:raw` / `:tls` / `:ws` / `:wss` / `:http` / `:https` ) a supported service this socket will listen to. |
1032
+ | `:timeout` | (HTTP only) keep-alive timeout in seconds. Up to 255 seconds. |
1033
+ | `:tls` | an {Iodine::TLS} context object for encrypted connections. |
1034
+
1035
+ Some connection settings are only valid when listening to HTTP / WebSocket connections.
1036
+
1037
+ If `:url` is provided, it will overwrite the `:address` and `:port` settings (if provided).
1038
+
1039
+ For HTTP connections, the `:handler` **must** be a valid Rack application object (answers `.call(env)`).
1040
+
1041
+ Here's an example for an HTTP hello world application:
1042
+
1043
+ require 'iodine'
1044
+ # a handler can be a block
1045
+ Iodine.listen(service: :http, port: "3000") {|env| [200, {"Content-Length" => "12"}, ["Hello World!"]] }
1046
+ # start the service
1047
+ Iodine.threads = 1
1048
+ Iodine.start
1049
+
1050
+
1051
+ Here's another example, using a Unix Socket instead of a TCP/IP socket for an HTTP hello world application.
1052
+
1053
+ This example shows how the `:url` option can be used, but the `:address` settings could have been used for the same effect (with `port: 0`).
1054
+
1055
+ require 'iodine'
1056
+ # note that unix sockets in URL form use an absolute path.
1057
+ Iodine.listen(url: "http://:0/tmp/sock.sock") {|env| [200, {"Content-Length" => "12"}, ["Hello World!"]] }
1058
+ # start the service
1059
+ Iodine.threads = 1
1060
+ Iodine.start
1061
+
1062
+
1063
+ For raw connections, the `:handler` object should be an object that answer `.call` and returns a valid callback object that supports the following callbacks (see also {Iodine::Connection}):
1064
+
1065
+ | | |
1066
+ |---|---|
1067
+ | `on_open(client)` | called after a connection was established |
1068
+ | `on_message(client,data)` | called when incoming data is available. Data may be fragmented. |
1069
+ | `on_drained(client)` | called after pending `client.write` events have been processed (see {Iodine::Connection#pending}). |
1070
+ | `ping(client)` | called whenever a timeout has occured (see {Iodine::Connection#timeout=}). |
1071
+ | `on_shutdown(client)` | called if the server is shutting down. This is called before the connection is closed. |
1072
+ | `on_close(client)` | called when the connection with the client was closed. |
1073
+
1074
+ The `client` argument passed to the `:handler` callbacks is an {Iodine::Connection} instance that represents the connection / the client.
1075
+
1076
+ Here's an example for a telnet based chat-room example:
1077
+
1078
+ require 'iodine'
1079
+ # define the protocol for our service
1080
+ module ChatHandler
1081
+ def self.on_open(client)
1082
+ # Set a connection timeout
1083
+ client.timeout = 10
1084
+ # subscribe to the chat channel.
1085
+ client.subscribe :chat
1086
+ # Write a welcome message
1087
+ client.publish :chat, "new member entered the chat\r\n"
1088
+ end
1089
+ # this is called for incoming data - note data might be fragmented.
1090
+ def self.on_message(client, data)
1091
+ # publish the data we received
1092
+ client.publish :chat, data
1093
+ # close the connection when the time comes
1094
+ client.close if data =~ /^bye[\n\r]/
1095
+ end
1096
+ # called whenever timeout occurs.
1097
+ def self.ping(client)
1098
+ client.write "System: quite, isn't it...?\r\n"
1099
+ end
1100
+ # called if the connection is still open and the server is shutting down.
1101
+ def self.on_shutdown(client)
1102
+ # write the data we received
1103
+ client.write "Chat server going away. Try again later.\r\n"
1104
+ end
1105
+ # returns the callback object (self).
1106
+ def self.call
1107
+ self
1108
+ end
1109
+ end
1110
+ # we use can both the `handler` keyword or a block, anything that answers #call.
1111
+ Iodine.listen(service: :raw, port: "3000", handler: ChatHandler)
1112
+ # we can listen to more than a single socket at a time.
1113
+ Iodine.listen(url: "raw://:3030", handler: ChatHandler)
1114
+ # start the service
1115
+ Iodine.threads = 1
1116
+ Iodine.start
1117
+
1118
+
1119
+
1120
+ Returns the handler object used.
1121
+ */
1122
+ static VALUE iodine_listen(VALUE self, VALUE args) {
1123
+ // clang-format on
1124
+ iodine_connection_args_s s = iodine_connect_args(args, 1);
1125
+ intptr_t uuid = -1;
1126
+ switch (s.service) {
1127
+ case IODINE_SERVICE_RAW:
1128
+ uuid = iodine_tcp_listen(s);
1129
+ break;
1130
+ case IODINE_SERVICE_HTTP: /* overflow */
1131
+ case IODINE_SERVICE_WS:
1132
+ uuid = iodine_http_listen(s);
1133
+ break;
1134
+ }
1135
+ iodine_connect_args_cleanup(&s);
1136
+ if (uuid == -1)
1137
+ rb_raise(rb_eRuntimeError, "Couldn't open listening socket.");
1138
+ return s.handler;
1139
+ (void)self;
1140
+ }
1141
+
1142
+ /* *****************************************************************************
1143
+ Connect function routing
1144
+ ***************************************************************************** */
1145
+
1146
+ // clang-format off
1147
+ /*
1148
+
1149
+ The {connect} method instructs iodine to connect to a server using either TCP/IP or Unix sockets.
1150
+
1151
+ Iodine.connect(settings)
1152
+
1153
+ Supported Settigs:
1154
+
1155
+
1156
+ | | |
1157
+ |---|---|
1158
+ | `:url` | URL indicating service type, host name, port and optional path. |
1159
+ | `:handler` | see details below. |
1160
+ | `:address` | an IP address or a unix socket address. Only relevant if `:url` is missing. |
1161
+ | `:body` | (HTTP client) the body to be sent. |
1162
+ | `:cookies` | (HTTP/WebSocket client) cookie data. |
1163
+ | `:headers` | (HTTP/WebSocket client) custom headers. |
1164
+ | `:log` | (HTTP only) - logging the requests. |
1165
+ | `:max_body` | (HTTP only) - limits HTTP body in the response, see {listen}. |
1166
+ | `:max_headers` | (HTTP only) - limits the header length in the response, see {listen}. |
1167
+ | `:max_msg` | (WebSockets only) maximum incoming message size pre message (in Kb). |
1168
+ | `:method` | (HTTP client) a String such as "GET" or "POST". |
1169
+ | `:path` |HTTP/WebSocket client) the HTTP path to be used. |
1170
+ | `:ping` | ping interval (in seconds). Up to 255 seconds. |
1171
+ | `:port` | port number to listen to either a String or Number) |
1172
+ | `:public` | (public folder, HTTP server only) |
1173
+ | `:service` | (`:raw` / `:tls` / `:ws` / `:wss` ) |
1174
+ | `:timeout` | (HTTP only) keep-alive timeout in seconds. Up to 255 seconds. |
1175
+ | `:tls` | an {Iodine::TLS} context object for encrypted connections. |
1176
+
1177
+ Some connection settings are only valid for HTTP / WebSocket connections.
1178
+
1179
+ If `:url` is provided, it will overwrite the `:address`, `:port` and `:path` settings (if provided).
1180
+
1181
+ Unlike {Iodine.listen}, a block can't be used and a `:handler` object **must** be provided.
1182
+
1183
+ If the connection fails, only the `on_close` callback will be called (with a `nil` client).
1184
+
1185
+ Here's an example TCP/IP client that sends a simple HTTP GET request:
1186
+
1187
+ # use a secure connection?
1188
+ USE_TLS = false
1189
+
1190
+ # remote server details
1191
+ $port = USE_TLS ? 443 : 80
1192
+ $address = "google.com"
1193
+
1194
+
1195
+ # require iodine
1196
+ require 'iodine'
1197
+
1198
+ # Iodine runtime settings
1199
+ Iodine.threads = 1
1200
+ Iodine.workers = 1
1201
+ Iodine.verbosity = 3 # warnings only
1202
+
1203
+
1204
+ # a client callback handler
1205
+ module Client
1206
+
1207
+ def self.on_open(connection)
1208
+ # Set a connection timeout
1209
+ connection.timeout = 10
1210
+ # subscribe to the chat channel.
1211
+ puts "* Sending request..."
1212
+ connection.write "GET / HTTP/1.1\r\nHost: #{$address}\r\n\r\n"
1213
+ end
1214
+
1215
+ def self.on_message(connection, data)
1216
+ # publish the data we received
1217
+ STDOUT.write data
1218
+ # close the connection after a second... we're not really parsing anything, so it's a guess.
1219
+ Iodine.run_after(1000) { connection.close }
1220
+ end
1221
+
1222
+ def self.on_close(connection)
1223
+ # stop iodine
1224
+ Iodine.stop
1225
+ puts "Done."
1226
+ end
1227
+
1228
+ # returns the callback object (self).
1229
+ def self.call
1230
+ self
1231
+ end
1232
+ end
1233
+
1234
+
1235
+
1236
+ if(USE_TLS)
1237
+ tls = Iodine::TLS.new
1238
+ # ALPN blocks should return a valid calback object
1239
+ tls.on_protocol("http/1.1") { Client }
1240
+ end
1241
+
1242
+ Iodine.connect(address: $address, port: $port, handler: Client, tls: tls)
1243
+
1244
+ # start the iodine reactor
1245
+ Iodine.start
1246
+
1247
+ Iodine also supports WebSocket client connections, using either the `url` property or the `ws` and `wss` service names.
1248
+
1249
+ The following example establishes a secure (TLS) connects to the WebSocket echo testing server at `wss://echo.websocket.org`:
1250
+
1251
+ # require iodine
1252
+ require 'iodine'
1253
+
1254
+ # The client class
1255
+ class EchoClient
1256
+
1257
+ def on_open(connection)
1258
+ @messages = [ "Hello World!",
1259
+ "I'm alive and sending messages",
1260
+ "I also receive messages",
1261
+ "now that we all know this...",
1262
+ "I can stop.",
1263
+ "Goodbye." ]
1264
+ send_one_message(connection)
1265
+ end
1266
+
1267
+ def on_message(connection, message)
1268
+ puts "Received: #{message}"
1269
+ send_one_message(connection)
1270
+ end
1271
+
1272
+ def on_close(connection)
1273
+ # in this example, we stop iodine once the client is closed
1274
+ puts "* Client closed."
1275
+ Iodine.stop
1276
+ end
1277
+
1278
+ # We use this method to pop messages from the queue and send them
1279
+ #
1280
+ # When the queue is empty, we disconnect the client.
1281
+ def send_one_message(connection)
1282
+ msg = @messages.shift
1283
+ if(msg)
1284
+ connection.write msg
1285
+ else
1286
+ connection.close
1287
+ end
1288
+ end
1289
+ end
1290
+
1291
+ Iodine.threads = 1
1292
+ Iodine.connect url: "wss://echo.websocket.org", handler: EchoClient.new, ping: 40
1293
+ Iodine.start
1294
+
1295
+ **Note**: the `on_close` callback is always called, even if a connection couldn't be established.
1296
+
1297
+ Returns the handler object used.
1298
+ */
1299
+ static VALUE iodine_connect(VALUE self, VALUE args) {
1300
+ // clang-format on
1301
+ iodine_connection_args_s s = iodine_connect_args(args, 0);
1302
+ intptr_t uuid = -1;
1303
+ switch (s.service) {
1304
+ case IODINE_SERVICE_RAW:
1305
+ uuid = iodine_tcp_connect(s);
1306
+ break;
1307
+ case IODINE_SERVICE_HTTP:
1308
+ iodine_connect_args_cleanup(&s);
1309
+ rb_raise(rb_eRuntimeError, "HTTP client connections aren't supported yet.");
1310
+ return Qnil;
1311
+ break;
1312
+ case IODINE_SERVICE_WS:
1313
+ uuid = iodine_ws_connect(s);
1314
+ break;
1315
+ }
1316
+ iodine_connect_args_cleanup(&s);
1317
+ if (uuid == -1)
1318
+ rb_raise(rb_eRuntimeError, "Couldn't open client socket.");
1319
+ return self;
1320
+ }
1321
+
1322
+ /* *****************************************************************************
1323
+ Ruby loads the library and invokes the Init_<lib_name> function...
1324
+
1325
+ Here we connect all the C code to the Ruby interface, completing the bridge
1326
+ between Lib-Server and Ruby.
1327
+ ***************************************************************************** */
1328
+ void Init_iodine(void) {
1329
+ /* common Symbol objects in use by Iodine */
1330
+ #define IODINE_MAKE_SYM(name) \
1331
+ do { \
1332
+ name##_sym = rb_id2sym(rb_intern(#name)); \
1333
+ rb_global_variable(&name##_sym); \
1334
+ } while (0)
1335
+ IODINE_MAKE_SYM(address);
1336
+ IODINE_MAKE_SYM(app);
1337
+ IODINE_MAKE_SYM(body);
1338
+ IODINE_MAKE_SYM(cookies);
1339
+ IODINE_MAKE_SYM(handler);
1340
+ IODINE_MAKE_SYM(headers);
1341
+ IODINE_MAKE_SYM(log);
1342
+ IODINE_MAKE_SYM(max_body);
1343
+ IODINE_MAKE_SYM(max_clients);
1344
+ IODINE_MAKE_SYM(max_headers);
1345
+ IODINE_MAKE_SYM(max_msg);
1346
+ IODINE_MAKE_SYM(method);
1347
+ IODINE_MAKE_SYM(path);
1348
+ IODINE_MAKE_SYM(ping);
1349
+ IODINE_MAKE_SYM(port);
1350
+ IODINE_MAKE_SYM(public);
1351
+ IODINE_MAKE_SYM(service);
1352
+ IODINE_MAKE_SYM(timeout);
1353
+ IODINE_MAKE_SYM(tls);
1354
+ IODINE_MAKE_SYM(url);
1355
+
1356
+ // load any environment specific patches
1357
+ patch_env();
1358
+
1359
+ // force the GVL state for the main thread
1360
+ IodineCaller.set_GVL(1);
1361
+
1362
+ // Create the Iodine module (namespace)
1363
+ IodineModule = rb_define_module("Iodine");
1364
+ IodineBaseModule = rb_define_module_under(IodineModule, "Base");
1365
+ VALUE IodineCLIModule = rb_define_module_under(IodineBaseModule, "CLI");
1366
+ iodine_call_id = rb_intern2("call", 4);
1367
+ iodine_to_s_id = rb_intern("to_s");
1368
+
1369
+ // register core methods
1370
+ rb_define_module_function(IodineModule, "threads", iodine_threads_get, 0);
1371
+ rb_define_module_function(IodineModule, "threads=", iodine_threads_set, 1);
1372
+ rb_define_module_function(IodineModule, "verbosity", iodine_logging_get, 0);
1373
+ rb_define_module_function(IodineModule, "verbosity=", iodine_logging_set, 1);
1374
+ rb_define_module_function(IodineModule, "workers", iodine_workers_get, 0);
1375
+ rb_define_module_function(IodineModule, "workers=", iodine_workers_set, 1);
1376
+ rb_define_module_function(IodineModule, "start", iodine_start, 0);
1377
+ rb_define_module_function(IodineModule, "stop", iodine_stop, 0);
1378
+ rb_define_module_function(IodineModule, "on_idle", iodine_sched_on_idle, 0);
1379
+ rb_define_module_function(IodineModule, "master?", iodine_master_is, 0);
1380
+ rb_define_module_function(IodineModule, "worker?", iodine_worker_is, 0);
1381
+ rb_define_module_function(IodineModule, "running?", iodine_running, 0);
1382
+ rb_define_module_function(IodineModule, "listen", iodine_listen, 1);
1383
+ rb_define_module_function(IodineModule, "connect", iodine_connect, 1);
1384
+
1385
+ // register CLI methods
1386
+ rb_define_module_function(IodineCLIModule, "parse", iodine_cli_parse, 0);
1387
+
1388
+ /** Default connection settings for {listen} and {connect}. */
1389
+ iodine_default_args = rb_hash_new();
1390
+ /** Default connection settings for {listen} and {connect}. */
1391
+ rb_const_set(IodineModule, rb_intern("DEFAULT_SETTINGS"),
1392
+ iodine_default_args);
1393
+
1394
+ /** Depracated, use {Iodine::DEFAULT_SETTINGS}. */
1395
+ rb_const_set(IodineModule, rb_intern("DEFAULT_HTTP_ARGS"),
1396
+ iodine_default_args);
1397
+
1398
+ // initialize Object storage for GC protection
1399
+ iodine_storage_init();
1400
+
1401
+ // initialize concurrency related methods
1402
+ iodine_defer_initialize();
1403
+
1404
+ // initialize the connection class
1405
+ iodine_connection_init();
1406
+
1407
+ // intialize the TCP/IP related module
1408
+ iodine_init_tcp_connections();
1409
+
1410
+ // initialize the HTTP module
1411
+ iodine_init_http();
1412
+
1413
+ #ifndef __MINGW32__
1414
+ // initialize SSL/TLS support module
1415
+ iodine_init_tls();
1416
+ #endif
1417
+
1418
+ // initialize JSON helpers
1419
+ iodine_init_json();
1420
+
1421
+ // initialize Mustache engine
1422
+ iodine_init_mustache();
1423
+
1424
+ // initialize Rack helpers and IO
1425
+ iodine_init_helpers();
1426
+ IodineRackIO.init();
1427
+
1428
+ // initialize Pub/Sub extension (for Engines)
1429
+ iodine_pubsub_init();
1430
+ }