iodine 0.7.60 → 0.8.0

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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -21
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -148
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. data/logo.png +0 -0
@@ -1,27 +1,236 @@
1
- #ifndef H_IODINE_CALLER_H
2
- #define H_IODINE_CALLER_H
3
-
4
- #include "ruby.h"
5
-
6
- #include <stdint.h>
7
-
8
- extern struct IodineCaller_s {
9
- /** Calls a C function within the GVL (unprotected). */
10
- void *(*enterGVL)(void *(*func)(void *), void *arg);
11
- /** Calls a C function outside the GVL (no Ruby API calls allowed). */
12
- void *(*leaveGVL)(void *(*func)(void *), void *arg);
13
- /** Calls a Ruby method on a given object, protecting against exceptions. */
14
- VALUE (*call)(VALUE obj, ID method);
15
- /** Calls a Ruby method on a given object, protecting against exceptions. */
16
- VALUE (*call2)(VALUE obj, ID method, int argc, VALUE *argv);
17
- /** Calls a Ruby method on a given object, protecting against exceptions. */
18
- VALUE(*call_with_block)
19
- (VALUE obj, ID method, int argc, VALUE *argv, VALUE udata,
20
- VALUE (*block_func)(VALUE block_argv1, VALUE udata, int argc, VALUE *argv));
21
- /** Returns the GVL state flag. */
22
- uint8_t (*in_GVL)(void);
23
- /** Forces the GVL state flag. */
24
- void (*set_GVL)(uint8_t state);
25
- } IodineCaller;
1
+ #ifndef H___IODINE_CALLER___H
2
+ #define H___IODINE_CALLER___H
3
+ #include "iodine.h"
4
+
5
+ /* *****************************************************************************
6
+ Ruby Caller and Thread (GVL) Helper
7
+
8
+ From within the GVL call Ruby functions so:
9
+
10
+ iodine_caller_result_s r = iodine_ruby_call_inside(recv, mid,
11
+ argc, argv, proc);
12
+
13
+ From outside the GVL call Ruby functions so:
14
+
15
+ iodine_caller_result_s r = iodine_ruby_call_outside(recv, mid, argc, argv);
16
+
17
+ When GVL state is ambiguous (may or may not hold it) call Ruby functions so:
18
+
19
+ iodine_caller_result_s r = iodine_ruby_call_anywhere(recv, mid, argc, argv);
20
+
21
+ ***************************************************************************** */
22
+
23
+ /* printout backtrace in case of exceptions */
24
+ static void *iodine_handle_exception(void *ignr) {
25
+ (void)ignr;
26
+ FIO_LOG_ERROR("(%d) iodine catching an exposed exception", fio_io_pid());
27
+ VALUE exc = rb_errinfo();
28
+ if (exc != Qnil && rb_respond_to(exc, rb_intern("message")) &&
29
+ rb_respond_to(exc, rb_intern("backtrace"))) {
30
+ VALUE msg = rb_funcallv(exc, rb_intern("message"), 0, NULL);
31
+ VALUE exc_class = rb_class_name(CLASS_OF(exc));
32
+ VALUE bt = rb_funcallv(exc, rb_intern("backtrace"), 0, NULL);
33
+ if (msg == Qnil)
34
+ msg = rb_str_new("Error message unavailable", 25);
35
+ if (exc_class == Qnil)
36
+ exc_class = rb_str_new("unknown exception class", 23);
37
+ if (TYPE(bt) == RUBY_T_ARRAY) {
38
+ bt = rb_ary_join(bt, rb_str_new_literal("\n"));
39
+ FIO_LOG_ERROR("exposed exception message: %.*s: %.*s\n %s ",
40
+ (int)RSTRING_LEN(exc_class),
41
+ RSTRING_PTR(exc_class),
42
+ (int)RSTRING_LEN(msg),
43
+ RSTRING_PTR(msg),
44
+ StringValueCStr(bt));
45
+ } else if (TYPE(bt) == RUBY_T_STRING) {
46
+ FIO_LOG_ERROR("exposed exception message: %.*s: %.*s\n"
47
+ "No backtrace available.\n",
48
+ (int)RSTRING_LEN(exc_class),
49
+ RSTRING_PTR(exc_class),
50
+ (int)RSTRING_LEN(msg),
51
+ RSTRING_PTR(msg));
52
+ } else {
53
+ FIO_LOG_ERROR("exposed exception message: %.*s: %.*s\n %s\n"
54
+ "BACKTRACE UNAVAILABLE!\n",
55
+ (int)RSTRING_LEN(exc_class),
56
+ RSTRING_PTR(exc_class),
57
+ (int)RSTRING_LEN(msg),
58
+ RSTRING_PTR(msg));
59
+ FIO_LOG_ERROR("Backtrace missing.");
60
+ }
61
+ rb_backtrace();
62
+ FIO_LOG_ERROR("\n");
63
+ rb_set_errinfo(Qnil);
64
+ } else if (exc != Qnil) {
65
+ FIO_LOG_ERROR("exposed exception message: NO MESSAGE / DATA AVAILABLE");
66
+ }
67
+ return (void *)Qnil;
68
+ }
69
+
70
+ typedef struct {
71
+ VALUE recv;
72
+ ID mid;
73
+ int argc;
74
+ VALUE *argv;
75
+ VALUE proc;
76
+ int ignore_exceptions;
77
+ } iodine_caller_args_s;
78
+
79
+ typedef struct {
80
+ VALUE result;
81
+ int exception;
82
+ } iodine_caller_result_s;
83
+
84
+ typedef struct {
85
+ iodine_caller_args_s in;
86
+ iodine_caller_result_s out;
87
+ } iodine___caller_s;
88
+
89
+ static VALUE iodine___func_caller_task(VALUE args_) {
90
+ iodine_caller_args_s *args = (iodine_caller_args_s *)args_;
91
+ return rb_funcallv(args->recv, args->mid, args->argc, args->argv);
92
+ }
93
+
94
+ static VALUE iodine___func_caller_task_proc(VALUE args_) {
95
+ iodine_caller_args_s *args = (iodine_caller_args_s *)args_;
96
+ return rb_funcall_with_block(args->recv,
97
+ args->mid,
98
+ args->argc,
99
+ args->argv,
100
+ args->proc);
101
+ }
102
+
103
+ static void *iodine_ruby____outside_task_proc(void *c_) {
104
+ iodine___caller_s *c = (iodine___caller_s *)c_;
105
+ c->out.result = rb_protect(iodine___func_caller_task_proc,
106
+ (VALUE)&c->in,
107
+ &c->out.exception);
108
+ if (c->out.exception && !c->in.ignore_exceptions)
109
+ iodine_handle_exception(NULL);
110
+ return NULL;
111
+ }
112
+
113
+ static void *iodine_ruby____outside_task(void *c_) {
114
+ iodine___caller_s *c = (iodine___caller_s *)c_;
115
+ c->out.result =
116
+ rb_protect(iodine___func_caller_task, (VALUE)&c->in, &c->out.exception);
117
+ if (c->out.exception && !c->in.ignore_exceptions)
118
+ iodine_handle_exception(NULL);
119
+ return NULL;
120
+ }
121
+
122
+ /* *****************************************************************************
123
+ Ruby Caller and Thread (GVL) - Helper Implementation
124
+ ***************************************************************************** */
125
+
126
+ /*
127
+ Calls a function from inside the GVL.
128
+
129
+ Accepts the following, possibly named, arguments:
130
+
131
+ (VALUE recv, ID mid, int argc,
132
+ VALUE *argv, VALUE proc, int ignore_exceptions)
133
+
134
+ */
135
+ inline static iodine_caller_result_s iodine_ruby_call_inside(
136
+ iodine_caller_args_s args) {
137
+ iodine_caller_result_s r = {0};
138
+ FIO_ASSERT_DEBUG(args.recv && args.mid,
139
+ "iodine_ruby_call requires an object and method name");
140
+ VALUE stub[1] = {Qnil};
141
+ if (!args.argv)
142
+ args.argv = stub;
143
+ r.result = rb_protect(
144
+ (args.proc ? iodine___func_caller_task_proc : iodine___func_caller_task),
145
+ (VALUE)&args,
146
+ &r.exception);
147
+ if (r.exception)
148
+ iodine_handle_exception(NULL);
149
+ return r;
150
+ }
151
+
152
+ /*
153
+ Calls a function from outside the GVL, acquiring the lock and then releasing it.
154
+
155
+ All call sites are from IO/worker threads that do NOT hold the GVL.
156
+ For ambiguous GVL state, use iodine_ruby_call_anywhere instead.
157
+
158
+ Accepts the following, possibly named, arguments:
159
+
160
+ (VALUE recv, ID mid, int argc,
161
+ VALUE *argv, VALUE proc, int ignore_exceptions)
162
+
163
+ */
164
+ inline static iodine_caller_result_s iodine_ruby_call_outside(
165
+ iodine_caller_args_s args) {
166
+ VALUE stub[1] = {Qnil};
167
+ if (!args.argv)
168
+ args.argv = stub;
169
+ iodine___caller_s r = {args, {0}};
170
+ FIO_ASSERT_DEBUG(args.recv && args.mid,
171
+ "iodine_ruby_call requires an object and method name");
172
+ void *(*fn)(void *) = args.proc ? iodine_ruby____outside_task_proc
173
+ : iodine_ruby____outside_task;
174
+ rb_thread_call_with_gvl(fn, &r);
175
+ return r.out;
176
+ }
177
+
178
+ /**
179
+ Calls a function from inside the GVL.
180
+
181
+ Accepts the following, possibly named, arguments:
182
+
183
+ (VALUE recv, ID mid, int argc,
184
+ VALUE *argv, VALUE proc, int ignore_exceptions)
185
+
186
+ */
187
+ #define iodine_ruby_call_inside(...) \
188
+ iodine_ruby_call_inside((iodine_caller_args_s){__VA_ARGS__})
189
+
190
+ /**
191
+ Calls a function from outside the GVL, aquiring the lock and then releasing it.
192
+
193
+ Accepts the following, possibly named, arguments:
194
+
195
+ (VALUE recv, ID mid, int argc,
196
+ VALUE *argv, VALUE proc, int ignore_exceptions)
197
+
198
+ */
199
+ #define iodine_ruby_call_outside(...) \
200
+ iodine_ruby_call_outside((iodine_caller_args_s){__VA_ARGS__})
201
+
202
+ /**
203
+ For code paths where the calling thread may or may not hold the GVL.
204
+
205
+ On Ruby 3.x and 4.x (current path): checks ruby_thread_has_gvl_p() at runtime.
206
+
207
+ NOTE: Ruby Feature #20750 (merged Dec 2025) made rb_thread_call_with_gvl()
208
+ GVL-lenient on Ruby 4.x — safe to call whether or not GVL is held.
209
+ The `&& 0` gate below preserves the safe runtime-check path on all versions
210
+ until the lenient behaviour is confirmed present in every supported Ruby 4.x
211
+ release used in CI. To activate the simplified Ruby 4 fast path, remove
212
+ `&& 0` once Ruby 4.0.1+ GVL-lenience is verified in CI.
213
+ */
214
+ #if RUBY_API_VERSION_MAJOR >= 4 && 0
215
+ #define iodine_ruby_call_anywhere(...) \
216
+ iodine_ruby_call_outside((iodine_caller_args_s){__VA_ARGS__})
217
+ #define iodine_c_call_with(fn, args) rb_thread_call_with_gvl(fn, args)
218
+ #define iodine_c_call_without(fn, args) \
219
+ rb_thread_call_without_gvl(fn, args, NULL, NULL)
220
+
221
+ #else
222
+ #define iodine_ruby_call_anywhere(...) \
223
+ (ruby_thread_has_gvl_p() \
224
+ ? iodine_ruby_call_inside \
225
+ : iodine_ruby_call_outside)((iodine_caller_args_s){__VA_ARGS__})
226
+
227
+ #define iodine_c_call_with(fn, args) \
228
+ (ruby_thread_has_gvl_p() ? (fn)(args) : rb_thread_call_with_gvl(fn, args))
229
+
230
+ #define iodine_c_call_without(fn, args) \
231
+ (ruby_thread_has_gvl_p() ? rb_thread_call_without_gvl(fn, args, NULL, NULL) \
232
+ : (fn)(args))
26
233
 
27
234
  #endif
235
+
236
+ #endif /* H___IODINE_CALLER___H */
@@ -0,0 +1,434 @@
1
+ #ifndef H___IODINE_CLI___H
2
+ #define H___IODINE_CLI___H
3
+ #include "iodine.h"
4
+
5
+ /* *****************************************************************************
6
+ Iodine CLI - Command Line Interface Parsing
7
+
8
+ This module provides the Iodine::Base::CLI Ruby module for parsing and
9
+ managing command-line arguments. It wraps facil.io's CLI parser with
10
+ Ruby-friendly access.
11
+
12
+ Features:
13
+ - Parses ARGV with support for named and positional arguments
14
+ - Supports various argument types: strings, integers, booleans
15
+ - Provides default values from environment variables (THREADS, WORKERS, etc.)
16
+ - Configures TLS, RESP3, clustering, and HTTP settings
17
+
18
+ CLI Options (parsed from command line):
19
+ - Address: -b/--bind, -p/--port
20
+ - Concurrency: -t/--threads, -w/--workers
21
+ - HTTP: --public, --max-line, --max-header, --max-body, --keep-alive, --log
22
+ - WebSocket/SSE: --ws-max-msg, --timeout
23
+ - TLS: --tls-self, --tls-name, --tls-cert, --tls-key, --tls-password
24
+ - Clustering: --broadcast, --secret
25
+ - RESP3: --redis, --redis-ping
26
+ - Hot Restart: --hot-restart, --config, --preload
27
+ - Misc: --verbose, --rack, --pid, --contained
28
+
29
+ Ruby API (Iodine::Base::CLI):
30
+ - CLI.parse(required) - Parse ARGV, returns Hash of options
31
+ - CLI[key] - Get CLI option value
32
+ - CLI[key] = value - Set CLI option value (before start only)
33
+
34
+ Environment Variables:
35
+ - THREADS, WORKERS - Default thread/worker counts
36
+ - ADDRESS, PORT - Default binding address/port
37
+ - SECRET - Cluster secret key
38
+ - IODINE_MTLS - Enable embedded TLS 1.3
39
+ ***************************************************************************** */
40
+
41
+ /** Maximum number of CLI arguments supported */
42
+ #define IODINE_CLI_LIMIT 256
43
+
44
+ static int iodine_cli_task(fio_buf_info_s name,
45
+ fio_buf_info_s val,
46
+ fio_cli_arg_e t,
47
+ void *h_) {
48
+ static long at = 0;
49
+ VALUE h = (VALUE)h_;
50
+ VALUE tmp = Qnil;
51
+ VALUE n = Qnil;
52
+ VALUE v = Qnil;
53
+ switch (t) {
54
+ case FIO_CLI_ARG_BOOL: v = Qtrue; break;
55
+ case FIO_CLI_ARG_INT: v = RB_LL2NUM(fio_atol(&val.buf)); break;
56
+ default: v = rb_str_new(val.buf, val.len); break;
57
+ }
58
+ STORE.cache(v);
59
+ if (name.buf) {
60
+ at = 0;
61
+ while (name.buf[0] == '-') {
62
+ ++name.buf;
63
+ --name.len;
64
+ }
65
+ n = rb_str_new(name.buf, name.len);
66
+ STORE.cache(n);
67
+ } else {
68
+ n = RB_INT2FIX(at);
69
+ ++at;
70
+ }
71
+ rb_hash_aset(h, n, v);
72
+ if (RB_TYPE_P(n, RUBY_T_STRING)) {
73
+ tmp = rb_str_intern(n);
74
+ STORE.cache((n = tmp));
75
+ rb_hash_aset(h, n, v);
76
+ }
77
+ return 0;
78
+ }
79
+
80
+ /* *****************************************************************************
81
+ Ruby Public API.
82
+ ***************************************************************************** */
83
+ /* used here, defined later */
84
+ static VALUE iodine_tls_default_set(VALUE klass, VALUE backend);
85
+
86
+ FIO_IFUNC bool iodine___mtls_env(void) {
87
+ bool ret = 0;
88
+ char *e = fio_sys_env("IODINE_MTLS");
89
+ if (!e)
90
+ return ret;
91
+ fio_str_info_s s = FIO_STR_INFO1(e);
92
+ fio_str_info_s untrue = FIO_STR_INFO1((char *)"false");
93
+ fio_str_info_s untrue2 = FIO_STR_INFO1((char *)"0");
94
+ ret = (bool)(1UL ^ ((unsigned)(FIO_STR_INFO_IS_EQ(s, untrue)) |
95
+ FIO_STR_INFO_IS_EQ(s, untrue2)));
96
+ return ret;
97
+ }
98
+
99
+ /** Read CLI as required data. */
100
+ static VALUE iodine_cli_parse(VALUE self, VALUE required) {
101
+ if (!RB_TYPE_P(rb_argv, RUBY_T_ARRAY))
102
+ rb_raise(rb_eException, "ARGV should be an Array!");
103
+ FIO_STR_INFO_TMP_VAR(desc, 2048);
104
+ FIO_STR_INFO_TMP_VAR(threads, 128);
105
+ FIO_STR_INFO_TMP_VAR(workers, 128);
106
+ const char *argv[IODINE_CLI_LIMIT];
107
+ const char *threads_env = NULL;
108
+ const char *workers_env = NULL;
109
+ long len = 0;
110
+ VALUE iodine_version = rb_const_get(iodine_rb_IODINE, rb_intern("VERSION"));
111
+
112
+ /* I don't know if Ruby promises a NUL separator, but fio_bstr does. */
113
+ if (rb_argv0 && RB_TYPE_P(rb_argv0, RUBY_T_STRING)) {
114
+ argv[len++] =
115
+ fio_bstr_write(NULL, RSTRING_PTR(rb_argv0), RSTRING_LEN(rb_argv0));
116
+ } else
117
+ argv[len++] = (const char *)"iodine";
118
+ for (long i = 0; len < IODINE_CLI_LIMIT && i < rb_array_len(rb_argv); ++i) {
119
+ VALUE o = rb_ary_entry(rb_argv, i);
120
+ if (!RB_TYPE_P(o, RUBY_T_STRING)) {
121
+ FIO_LOG_WARNING("ARGV member skipped - not a String!");
122
+ continue;
123
+ }
124
+ argv[len++] = fio_bstr_write(NULL, RSTRING_PTR(o), RSTRING_LEN(o));
125
+ }
126
+
127
+ /* in case of `-h` or error, make sure we clean up */
128
+ for (long i = 0; i < len; ++i)
129
+ fio_state_callback_add(FIO_CALL_AT_EXIT,
130
+ (void (*)(void *))fio_bstr_free,
131
+ (void *)argv[i]);
132
+
133
+ threads_env = fio_sys_env("THREADS");
134
+ workers_env = fio_sys_env("WORKERS");
135
+
136
+ fio_string_write2(
137
+ &threads,
138
+ NULL,
139
+ FIO_STRING_WRITE_STR1("--threads -t ("),
140
+ (threads_env ? FIO_STRING_WRITE_STR1(threads_env)
141
+ : FIO_STRING_WRITE_STR1("-4")),
142
+ FIO_STRING_WRITE_STR1(") number of worker threads to use."));
143
+ fio_string_write2(
144
+ &workers,
145
+ NULL,
146
+ FIO_STRING_WRITE_STR1("--workers -w ("),
147
+ (workers_env ? FIO_STRING_WRITE_STR1(workers_env)
148
+ : FIO_STRING_WRITE_STR1("-2")),
149
+ FIO_STRING_WRITE_STR1(") number of worker processes to use."));
150
+
151
+ fio_string_write2(
152
+ &desc,
153
+ NULL,
154
+ FIO_STRING_WRITE_STR1("Iodine's (" FIO_POLL_ENGINE_STR
155
+ ") HTTP/WebSocket server version "),
156
+ FIO_STRING_WRITE_STR2(RSTRING_PTR(iodine_version),
157
+ (size_t)RSTRING_LEN(iodine_version)),
158
+ FIO_STRING_WRITE_STR1(
159
+ "\r\n\r\nUse:\r\n iodine <options> <filename>\r\n\r\n"
160
+ "Both <options> and <filename> are optional. i.e.,:\r\n"
161
+ " iodine -p 0 -b /tmp/my_unix_sock\r\n"
162
+ " iodine -p 8080 path/to/app/config.ru\r\n"
163
+ " iodine -p 8080 path/to/app/config.nru\r\n"
164
+ " iodine -p 8080 -w 4 -t 16\r\n"
165
+ " iodine -w -1 -t 4 -r redis://usr:pass@localhost:6379/\r\n\r\n"
166
+ "Default <filename> if unspecified: `config.nru` and `config.ru`"));
167
+
168
+ fio_cli_end();
169
+ fio_cli_start(
170
+ (int)len,
171
+ (const char **)argv,
172
+ 0,
173
+ ((required == Qnil || required == Qfalse) ? -1 : 1),
174
+ desc.buf,
175
+ FIO_CLI_PRINT_HEADER("Address Binding"),
176
+ FIO_CLI_PRINT_LINE(
177
+ "NOTE: also controlled by the ADDRESS or PORT environment vars."),
178
+ FIO_CLI_STRING(
179
+ "-bind -b address to listen to in URL format (MAY include PORT)."),
180
+ FIO_CLI_PRINT(
181
+ "It's possible to add TLS/SSL data to the binding URL. i.e.:"),
182
+ FIO_CLI_PRINT("\t iodine -b https://0.0.0.0/?tls=./cert_path/"),
183
+ FIO_CLI_PRINT(
184
+ "\t iodine -b https://0.0.0.0/?key=./key.pem&cert=./cert.pem"),
185
+ FIO_CLI_PRINT("TLS client authentication / trust is available. i.e.:"),
186
+ FIO_CLI_PRINT("\t iodine -b https://0.0.0.0/?trust=ca.pem"),
187
+ FIO_CLI_PRINT("\t iodine -b https://0.0.0.0/?trust=system"),
188
+ FIO_CLI_INT("-port -p default port number to listen to."),
189
+ FIO_CLI_PRINT(
190
+ "Note: these are optional and supersede previous instructions."),
191
+
192
+ FIO_CLI_PRINT_HEADER("Concurrency"),
193
+ FIO_CLI_INT(threads.buf),
194
+ FIO_CLI_INT(workers.buf),
195
+
196
+ FIO_CLI_PRINT_HEADER("HTTP"),
197
+ FIO_CLI_STRING("--public -www public folder for static file service."),
198
+ FIO_CLI_INT("--max-line -maxln (" FIO_MACRO2STR(
199
+ FIO_HTTP_DEFAULT_MAX_LINE_LEN) ") per-header line limit, in bytes."),
200
+ FIO_CLI_INT("--max-header -maxhd (" FIO_MACRO2STR(
201
+ FIO_HTTP_DEFAULT_MAX_HEADER_SIZE) ") total header limit per request, "
202
+ "in bytes."),
203
+ FIO_CLI_INT("--max-body -maxbd (" FIO_MACRO2STR(
204
+ FIO_HTTP_DEFAULT_MAX_BODY_SIZE) ") total message payload limit per "
205
+ "request, in bytes."),
206
+ FIO_CLI_INT("--keep-alive -k (" FIO_MACRO2STR(
207
+ FIO_HTTP_DEFAULT_TIMEOUT) ") HTTP keep-alive timeout in seconds "
208
+ "(0..255)"),
209
+ FIO_CLI_INT("--max-age -maxage (3600) default Max-Age header value for "
210
+ "static files."),
211
+ FIO_CLI_BOOL("--no-dynamic-deflate -no-dynd Disable dynamic deflation of "
212
+ "HTTP replies."),
213
+ FIO_CLI_BOOL("--log -v log HTTP messages."),
214
+
215
+ FIO_CLI_PRINT_HEADER("WebSocket / SSE"),
216
+ FIO_CLI_BOOL("--no-websocket-deflate -no-wsd Disable WebSocket "
217
+ "permessage-deflate."),
218
+ FIO_CLI_INT("--ws-max-msg -maxms (" FIO_MACRO2STR(
219
+ FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE) ") incoming WebSocket message "
220
+ "limit, in bytes."),
221
+ FIO_CLI_INT("--timeout -ping (" FIO_MACRO2STR(
222
+ FIO_HTTP_DEFAULT_TIMEOUT_LONG) ") WebSocket / SSE timeout, in "
223
+ "seconds."),
224
+
225
+ FIO_CLI_PRINT_HEADER("TLS / SSL"),
226
+ FIO_CLI_PRINT(
227
+ "NOTE: may crash if no crypto implementation is available."),
228
+ FIO_CLI_BOOL(
229
+ "--tls-self -tls uses SSL/TLS with a self signed certificate."),
230
+ FIO_CLI_STRING("--tls-name -name The host name for the SSL/TLS "
231
+ "certificate (if any)."),
232
+ FIO_CLI_STRING("--tls-cert -cert The SSL/TLS certificate .pem file."),
233
+ FIO_CLI_STRING("--tls-key -key The SSL/TLS private key .pem file."),
234
+ FIO_CLI_STRING(
235
+ "--tls-password -tls-pass The SSL/TLS password for the private key."),
236
+ FIO_CLI_BOOL("--tls-embedded -mtls uses facil.io's minimal TLS 1.3 "
237
+ "implementation."),
238
+ FIO_CLI_PRINT(
239
+ "NOTE: facil.io's TLS 1.3 was never audited, but could be useful."),
240
+
241
+ FIO_CLI_PRINT_HEADER("Clustering Pub/Sub"),
242
+ FIO_CLI_INT("--broadcast -bp Cluster Broadcast Port."),
243
+ FIO_CLI_STRING("--secret -scrt Cluster Secret."),
244
+ FIO_CLI_PRINT("NOTE: also controlled by the SECRET and SECRET_LENGTH "
245
+ "environment vars."),
246
+
247
+ FIO_CLI_PRINT_HEADER("Connecting Iodine to a RESP3 Database:"),
248
+ FIO_CLI_STRING(
249
+ "--redis -r an optional RESP3 database URL (e.g., Valkey or Redis)."),
250
+ FIO_CLI_INT("--redis-ping -rp RESP3 ping interval in seconds."),
251
+
252
+ FIO_CLI_PRINT_HEADER("Hot Application Swapping / Restarts"),
253
+ FIO_CLI_PRINT_LINE(
254
+ "(requires cluster mode, app code runs only in workers)"),
255
+ FIO_CLI_INT(
256
+ "--hot-restart -hr restarts all worker processes every n seconds."),
257
+ FIO_CLI_STRING(
258
+ "--config -C (config/boot.rb) configuration file to be loaded."),
259
+ FIO_CLI_PRINT("NOTE: the code in the --config file won't be updated."),
260
+ FIO_CLI_BOOL("--preload -preload loads the app code before forking "
261
+ "(cluster only)."),
262
+ FIO_CLI_PRINT(
263
+ "NOTE: otherwise only the --config file will run in root process."),
264
+
265
+ FIO_CLI_PRINT_HEADER("Misc"),
266
+ FIO_CLI_BOOL("--verbose -V -d print out debugging messages."),
267
+ FIO_CLI_BOOL("--rack -R -rack prefer Rack::Builder over NeoRack."),
268
+ FIO_CLI_STRING(
269
+ "--pid -pidfile -pid name for the pid file to be created."),
270
+ FIO_CLI_BOOL(
271
+ "--contained attempts to handle possible container restrictions."),
272
+ FIO_CLI_PRINT(
273
+ "Containers sometimes impose file-system restrictions, i.e.,"),
274
+ FIO_CLI_PRINT("the IPC Unix Socket might need to be placed in `/tmp`."));
275
+
276
+ /* review CLI for logging */
277
+ if (fio_cli_get_bool("-V")) {
278
+ FIO_LOG_LEVEL = FIO_LOG_LEVEL_DEBUG;
279
+ }
280
+
281
+ if (fio_cli_get_bool("-mtls") || iodine___mtls_env()) {
282
+ fio_io_functions_s io_fn;
283
+ io_fn = fio_tls13_io_functions();
284
+ fio_io_tls_default_functions(&io_fn);
285
+ iodine_tls_default_set(Qnil, ID2SYM(rb_intern("iodine")));
286
+ fio_cli_set("-tls", "1");
287
+ FIO_LOG_DEBUG2("TLS 1.3 registered as default TLS implementation");
288
+ }
289
+
290
+ if (fio_cli_get_bool("--contained")) { /* container - IPC url in tmp */
291
+ char *u = (char *)fio_ipc_url();
292
+ memcpy((void *)(u + 7), "/tmp/", 5);
293
+ }
294
+
295
+ /* Secret Detection */
296
+ if (fio_cli_get("-scrt")) {
297
+ fio_buf_info_s scrt = FIO_BUF_INFO1((char *)fio_cli_get("-scrt"));
298
+ fio_secret_set(scrt.buf, scrt.len, 0);
299
+ }
300
+
301
+ /* Clustering */
302
+ if (!fio_cli_get_i("-bp") && fio_sys_env("PUBSUB_PORT")) {
303
+ char *bp_s = fio_sys_env("PUBSUB_PORT");
304
+ int64_t bp = fio_atol(&bp_s);
305
+ if (bp > 0 && bp < 0xFFFFLL)
306
+ fio_cli_set("-bp", fio_sys_env("PUBSUB_PORT"));
307
+ }
308
+ if (fio_cli_get_i("-bp") > 0) {
309
+ fio_ipc_cluster_listen((uint16_t)fio_cli_get_i("-bp"));
310
+ }
311
+
312
+ /* support -b and -p for when a URL isn't provided */
313
+ if (fio_cli_get("-p")) {
314
+ fio_buf_info_s tmp = fio_cli_get_str("-b");
315
+ if (tmp.buf) {
316
+ FIO_STR_INFO_TMP_VAR(url, 2048);
317
+ FIO_ASSERT(tmp.len < 2000, "binding address / url too long.");
318
+ fio_url_s u = fio_url_parse(tmp.buf, tmp.len);
319
+ tmp.buf = (char *)fio_cli_get("-p");
320
+ tmp.len = strlen(tmp.buf);
321
+ FIO_ASSERT(tmp.len < 6, "port number too long.");
322
+ fio_string_write2(&url,
323
+ NULL,
324
+ FIO_STRING_WRITE_STR2(u.scheme.buf, u.scheme.len),
325
+ (u.scheme.len ? FIO_STRING_WRITE_STR2("://", 3)
326
+ : FIO_STRING_WRITE_STR2(NULL, 0)),
327
+ FIO_STRING_WRITE_STR2(u.host.buf, u.host.len),
328
+ FIO_STRING_WRITE_STR2(":", 1),
329
+ FIO_STRING_WRITE_STR2(tmp.buf, tmp.len),
330
+ (u.query.len ? FIO_STRING_WRITE_STR2("?", 1)
331
+ : FIO_STRING_WRITE_STR2(NULL, 0)),
332
+ FIO_STRING_WRITE_STR2(u.query.buf, u.query.len));
333
+ fio_cli_set("-b", url.buf);
334
+ } else {
335
+ fio_sys_env_set("PORT", fio_cli_get("-p"), 1);
336
+ }
337
+ }
338
+
339
+ /* Save data to Hash and return it... why? I don't know. */
340
+ VALUE h = rb_hash_new();
341
+ STORE.hold(h);
342
+ fio_cli_each(iodine_cli_task, (void *)h);
343
+ /* cleanup */
344
+ for (long i = 0; i < len; ++i) {
345
+ fio_state_callback_remove(FIO_CALL_AT_EXIT,
346
+ (void (*)(void *))fio_bstr_free,
347
+ (void *)argv[i]);
348
+ fio_bstr_free((char *)argv[i]);
349
+ }
350
+ STORE.release(h);
351
+ return h;
352
+ }
353
+
354
+ static VALUE iodine_cli_get(VALUE self, VALUE key) {
355
+ VALUE r = Qnil;
356
+ fio_buf_info_s val;
357
+ int64_t ival = 0;
358
+ char *tmp;
359
+ if (RB_INTEGER_TYPE_P(key)) {
360
+ val = fio_cli_unnamed_str(NUM2UINT(key));
361
+ if (val.buf)
362
+ r = rb_str_new(val.buf, val.len);
363
+ return r;
364
+ }
365
+ if (RB_TYPE_P(key, RUBY_T_SYMBOL))
366
+ key = rb_sym2str(key);
367
+ if (!RB_TYPE_P(key, RUBY_T_STRING))
368
+ rb_raise(rb_eArgError,
369
+ "key should be either an Integer, a String or a Symbol");
370
+ val = fio_cli_get_str(RSTRING_PTR(key));
371
+ if (!val.len)
372
+ return r;
373
+ tmp = val.buf;
374
+ ival = fio_atol(&tmp);
375
+ if (tmp == val.buf + val.len)
376
+ r = LL2NUM(ival);
377
+ else
378
+ r = rb_str_new(val.buf, val.len);
379
+ return r;
380
+ }
381
+
382
+ static VALUE iodine_cli_set(VALUE self, VALUE key, VALUE value) {
383
+ if (fio_io_is_running() || !fio_io_is_master())
384
+ rb_raise(rb_eException,
385
+ "Setting CLI arguments can only be performed before Iodine.start "
386
+ "and in the master process.");
387
+ if (RB_INTEGER_TYPE_P(key)) {
388
+ if (!RB_TYPE_P(value, RUBY_T_STRING))
389
+ rb_raise(rb_eArgError,
390
+ "value for an indexed CLI argument should be a String");
391
+ fio_cli_set_unnamed(NUM2UINT(key), RSTRING_PTR(value));
392
+ return value;
393
+ }
394
+ if (RB_TYPE_P(key, RUBY_T_SYMBOL))
395
+ key = rb_sym2str(key);
396
+ if (!RB_TYPE_P(key, RUBY_T_STRING))
397
+ rb_raise(rb_eArgError,
398
+ "key should be either an Integer, a String or a Symbol");
399
+ fio_cli_arg_e t = fio_cli_type(RSTRING_PTR(key));
400
+ if (t == FIO_CLI_ARG_INT) { /* Number */
401
+ if (!RB_INTEGER_TYPE_P(value))
402
+ rb_raise(rb_eArgError,
403
+ "value for %s should be an Integer",
404
+ RSTRING_PTR(key));
405
+ fio_cli_set_i(RSTRING_PTR(key), NUM2LL(value));
406
+ } else if (t == FIO_CLI_ARG_BOOL) { /* Boolean */
407
+ if (value != Qtrue && value != Qfalse)
408
+ rb_raise(rb_eArgError,
409
+ "value for %s should be a Boolean",
410
+ RSTRING_PTR(key));
411
+ fio_cli_set_i(RSTRING_PTR(key), NUM2LL((value == Qtrue)));
412
+
413
+ } else { /* must be String */
414
+ if (!RB_TYPE_P(value, RUBY_T_STRING))
415
+ rb_raise(rb_eArgError,
416
+ "value for %s should be a String",
417
+ RSTRING_PTR(key));
418
+ fio_cli_set(RSTRING_PTR(key), RSTRING_PTR(value));
419
+ }
420
+ return value;
421
+ }
422
+
423
+ /** Initialize Iodine::Base::CLI */
424
+ /**
425
+ * The Iodine::Base::CLI module is used internally to manage CLI options.
426
+ */
427
+ static void Init_Iodine_Base_CLI(void) {
428
+ VALUE cli = rb_define_module_under(iodine_rb_IODINE_BASE, "CLI");
429
+ rb_define_singleton_method(cli, "parse", iodine_cli_parse, 1);
430
+ rb_define_singleton_method(cli, "[]", iodine_cli_get, 1);
431
+ rb_define_singleton_method(cli, "[]=", iodine_cli_set, 2);
432
+ iodine_cli_parse(cli, Qfalse);
433
+ }
434
+ #endif /* H___IODINE_CLI___H */