itsi-server 0.1.1 → 0.1.18

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +7 -0
  4. data/Cargo.lock +3937 -0
  5. data/Cargo.toml +7 -0
  6. data/README.md +4 -0
  7. data/Rakefile +8 -1
  8. data/_index.md +6 -0
  9. data/exe/itsi +141 -46
  10. data/ext/itsi_error/Cargo.toml +3 -0
  11. data/ext/itsi_error/src/lib.rs +98 -24
  12. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  13. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  14. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  15. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  16. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  17. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  18. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  19. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  20. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  21. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  22. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  23. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  24. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  25. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  26. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  27. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  28. data/ext/itsi_rb_helpers/Cargo.toml +3 -0
  29. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  30. data/ext/itsi_rb_helpers/src/lib.rs +140 -10
  31. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  32. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  33. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  34. data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  35. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  36. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  37. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  38. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  39. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  40. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  41. data/ext/itsi_scheduler/Cargo.toml +24 -0
  42. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  43. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  44. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  45. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  46. data/ext/itsi_scheduler/src/lib.rs +38 -0
  47. data/ext/itsi_server/Cargo.lock +2956 -0
  48. data/ext/itsi_server/Cargo.toml +72 -14
  49. data/ext/itsi_server/extconf.rb +1 -1
  50. data/ext/itsi_server/src/default_responses/html/401.html +68 -0
  51. data/ext/itsi_server/src/default_responses/html/403.html +68 -0
  52. data/ext/itsi_server/src/default_responses/html/404.html +68 -0
  53. data/ext/itsi_server/src/default_responses/html/413.html +71 -0
  54. data/ext/itsi_server/src/default_responses/html/429.html +68 -0
  55. data/ext/itsi_server/src/default_responses/html/500.html +71 -0
  56. data/ext/itsi_server/src/default_responses/html/502.html +71 -0
  57. data/ext/itsi_server/src/default_responses/html/503.html +68 -0
  58. data/ext/itsi_server/src/default_responses/html/504.html +69 -0
  59. data/ext/itsi_server/src/default_responses/html/index.html +238 -0
  60. data/ext/itsi_server/src/default_responses/json/401.json +6 -0
  61. data/ext/itsi_server/src/default_responses/json/403.json +6 -0
  62. data/ext/itsi_server/src/default_responses/json/404.json +6 -0
  63. data/ext/itsi_server/src/default_responses/json/413.json +6 -0
  64. data/ext/itsi_server/src/default_responses/json/429.json +6 -0
  65. data/ext/itsi_server/src/default_responses/json/500.json +6 -0
  66. data/ext/itsi_server/src/default_responses/json/502.json +6 -0
  67. data/ext/itsi_server/src/default_responses/json/503.json +6 -0
  68. data/ext/itsi_server/src/default_responses/json/504.json +6 -0
  69. data/ext/itsi_server/src/default_responses/mod.rs +11 -0
  70. data/ext/itsi_server/src/env.rs +43 -0
  71. data/ext/itsi_server/src/lib.rs +132 -40
  72. data/ext/itsi_server/src/prelude.rs +2 -0
  73. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
  74. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +143 -0
  75. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  76. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
  77. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +345 -0
  78. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +391 -0
  79. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  80. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +375 -0
  81. data/ext/itsi_server/src/ruby_types/itsi_server.rs +83 -0
  82. data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
  83. data/ext/itsi_server/src/server/binds/bind.rs +201 -0
  84. data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
  85. data/ext/itsi_server/src/server/binds/listener.rs +432 -0
  86. data/ext/itsi_server/src/server/binds/mod.rs +4 -0
  87. data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
  88. data/ext/itsi_server/src/server/binds/tls.rs +270 -0
  89. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  90. data/ext/itsi_server/src/server/http_message_types.rs +97 -0
  91. data/ext/itsi_server/src/server/io_stream.rs +105 -0
  92. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +165 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +56 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +87 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +86 -0
  97. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +285 -0
  98. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +142 -0
  99. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +289 -0
  100. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +292 -0
  101. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +55 -0
  102. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
  103. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +157 -0
  104. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +195 -0
  105. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
  106. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +201 -0
  107. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  108. data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  109. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +87 -0
  110. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +414 -0
  111. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +131 -0
  112. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  113. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +44 -0
  114. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +36 -0
  115. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
  116. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +180 -0
  117. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  118. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +163 -0
  119. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  120. data/ext/itsi_server/src/server/middleware_stack/mod.rs +347 -0
  121. data/ext/itsi_server/src/server/mod.rs +12 -5
  122. data/ext/itsi_server/src/server/process_worker.rs +247 -0
  123. data/ext/itsi_server/src/server/request_job.rs +11 -0
  124. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +342 -0
  125. data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
  126. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
  127. data/ext/itsi_server/src/server/signal.rs +76 -0
  128. data/ext/itsi_server/src/server/size_limited_incoming.rs +101 -0
  129. data/ext/itsi_server/src/server/thread_worker.rs +475 -0
  130. data/ext/itsi_server/src/services/cache_store.rs +74 -0
  131. data/ext/itsi_server/src/services/itsi_http_service.rs +239 -0
  132. data/ext/itsi_server/src/services/mime_types.rs +1416 -0
  133. data/ext/itsi_server/src/services/mod.rs +6 -0
  134. data/ext/itsi_server/src/services/password_hasher.rs +83 -0
  135. data/ext/itsi_server/src/services/rate_limiter.rs +569 -0
  136. data/ext/itsi_server/src/services/static_file_server.rs +1324 -0
  137. data/ext/itsi_tracing/Cargo.toml +5 -0
  138. data/ext/itsi_tracing/src/lib.rs +315 -7
  139. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  140. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  141. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  142. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  143. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  144. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  145. data/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
  146. data/lib/itsi/http_request.rb +186 -0
  147. data/lib/itsi/http_response.rb +41 -0
  148. data/lib/itsi/passfile.rb +109 -0
  149. data/lib/itsi/server/config/dsl.rb +565 -0
  150. data/lib/itsi/server/config.rb +166 -0
  151. data/lib/itsi/server/default_app/default_app.rb +34 -0
  152. data/lib/itsi/server/default_app/index.html +115 -0
  153. data/lib/itsi/server/default_config/Itsi-rackup.rb +119 -0
  154. data/lib/itsi/server/default_config/Itsi.rb +107 -0
  155. data/lib/itsi/server/grpc/grpc_call.rb +246 -0
  156. data/lib/itsi/server/grpc/grpc_interface.rb +100 -0
  157. data/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  158. data/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  159. data/lib/itsi/server/rack/handler/itsi.rb +27 -0
  160. data/lib/itsi/server/rack_interface.rb +94 -0
  161. data/lib/itsi/server/route_tester.rb +107 -0
  162. data/lib/itsi/server/scheduler_interface.rb +21 -0
  163. data/lib/itsi/server/scheduler_mode.rb +10 -0
  164. data/lib/itsi/server/signal_trap.rb +29 -0
  165. data/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
  166. data/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
  167. data/lib/itsi/server/typed_handlers.rb +17 -0
  168. data/lib/itsi/server/version.rb +1 -1
  169. data/lib/itsi/server.rb +160 -9
  170. data/lib/itsi/standard_headers.rb +86 -0
  171. data/lib/ruby_lsp/itsi/addon.rb +111 -0
  172. data/lib/shell_completions/completions.rb +26 -0
  173. metadata +182 -25
  174. data/ext/itsi_server/src/request/itsi_request.rs +0 -143
  175. data/ext/itsi_server/src/request/mod.rs +0 -1
  176. data/ext/itsi_server/src/server/bind.rs +0 -138
  177. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  178. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  179. data/ext/itsi_server/src/server/itsi_server.rs +0 -182
  180. data/ext/itsi_server/src/server/listener.rs +0 -218
  181. data/ext/itsi_server/src/server/tls.rs +0 -138
  182. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  183. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
  184. data/lib/itsi/request.rb +0 -39
data/Cargo.toml ADDED
@@ -0,0 +1,7 @@
1
+ # This Cargo.toml is here to let externals tools (IDEs, etc.) know that this is
2
+ # a Rust project. Your extensions dependencies should be added to the Cargo.toml
3
+ # in the ext/ directory.
4
+
5
+ [workspace]
6
+ members = ["./ext/itsi_server"]
7
+ resolver = "2"
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ type: docs
3
+ ---
4
+
1
5
  # ItsiServer
2
6
 
3
7
  TODO: Delete this and the text below, and describe your gem
data/Rakefile CHANGED
@@ -3,7 +3,14 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "minitest/test_task"
5
5
 
6
- Minitest::TestTask.create
6
+
7
+ Minitest::TestTask.create(:test) do |t|
8
+ t.libs << 'test'
9
+ t.libs << 'lib'
10
+ t.warning = false
11
+ t.test_globs = ['test/**/*.rb']
12
+ t.test_prelude = 'require "helpers/test_helper.rb"'
13
+ end
7
14
 
8
15
  require "rubocop/rake_task"
9
16
 
data/_index.md ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: Itsi Server
3
+ type: docs
4
+ ---
5
+
6
+ ## Foo
data/exe/itsi CHANGED
@@ -1,59 +1,115 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "itsi/server"
4
5
  require "optparse"
5
- require "rack"
6
- require "etc"
7
-
8
- # Default options used when starting Osprey from the CLI using `osprey`
9
- DEFAULT_OPTIONS = {
10
- # Number of workers
11
- workers: Etc.nprocessors,
12
- # Number of threads per worker
13
- threads: 1,
14
- # Graceful shutdown timeout
15
- shutdown_timeout: 0.3,
16
- # Binds
17
- binds: ['http://0.0.0.0:3000']
6
+
7
+
8
+ COMMANDS = {
9
+ "init" => "Initialize a new Itsi.rb server configuration file",
10
+ "status" => "Show the status of the server",
11
+ "start" => "Start the Isti server",
12
+ "serve" => "Start the Isti server",
13
+ "stop" => "Stop the server",
14
+ "reload" => "Reload the server",
15
+ "restart" => "Restart the server",
16
+ "add_worker" => "Add a new worker to the server cluster",
17
+ "remove_worker" => "Remove a worker from the server cluster",
18
+ "routes" => "Print the routes of the server",
19
+ "passfile" => "Manage hashed users and passwords in a passfile (like .htpasswd). [add, remove, list]",
20
+ "test_route" => "Test which route a request will be routed to",
21
+ "static" => "Serve static assets in the given directory"
18
22
  }
19
23
 
20
- options = DEFAULT_OPTIONS.to_a.select(&:last).to_h
24
+ Itsi::Server::Config.prep_reexec!
25
+
26
+ options = {}
21
27
 
22
- # Define the option parser
23
- OptionParser.new do |opts|
24
- opts.banner = "Usage: script.rb [options]"
28
+ parser = OptionParser.new do |opts|
29
+ opts.banner = "Usage: itsi [COMMAND] [options]"
25
30
 
26
- opts.on("-w", "--workers WORKERS", Integer, "Number of workers (default: #{options[:workers]})") do |w|
31
+ opts.on("-C", "--config CONFIG_FILE", String, "Itsi Configuration file to use (default: Itsi.rb)") do |config_file|
32
+ options[:config_file] = config_file
33
+ end
34
+
35
+ opts.on("-w", "--workers WORKERS", Integer, "Number of workers") do |w|
27
36
  options[:workers] = w
28
37
  end
29
38
 
30
- opts.on("-t", "--threads THREADS", Integer, "Number of threads (default: #{options[:threads]})") do |t|
39
+ opts.on("-d", "--daemonize", "Run the process as a daemon") do
40
+ Process.daemon(true)
41
+ end
42
+
43
+ opts.on("-t", "--threads THREADS", Integer, "Number of threads (default: 1)") do |t|
31
44
  options[:threads] = t
32
45
  end
33
46
 
34
- opts.on("-h", "--host HOST", String, "Host to bind to (default: #{options[:host]})") do |h|
35
- options[:host] = h
47
+ opts.on("--[no-]multithreaded-reactor", "Use a multithreaded reactor") do |mtr|
48
+ options[:multithreaded_reactor] = mtr
49
+ end
50
+
51
+ opts.on("-r", "--rackup_file FILE", String, "Rackup file to use (default: config.ru)") do |rf|
52
+ options[:rackup_file] = rf
36
53
  end
37
54
 
38
- opts.on("-p", "--port PORT", Integer, "Port for the application (default: #{options[:port]})") do |p|
39
- options[:port] = p
55
+ opts.on("--worker-memory-limit MEMORY_LIMIT", Integer,
56
+ "Memory limit for each worker (default: None). If this limit is breached the worker is gracefully restarted") do |ml|
57
+ options[:worker_memory_limit] = ml
40
58
  end
41
59
 
42
- opts.on("-f", "--use_fiber_scheduler PORT", TrueClass,
43
- "Port for the application (default: #{options[:use_scheduler]})") do |p|
44
- options[:use_scheduler] = p
60
+ opts.on("-f", "--fiber_scheduler [CLASS_NAME]", [String],
61
+ "Scheduler class to use (default: nil). Provide blank or true to use Itsi::Scheduler, or a classname to use an alternative scheduler") do |scheduler_class|
62
+ if scheduler_class.nil? || scheduler_class == "true"
63
+ options[:scheduler_class] = "Itsi::Scheduler"
64
+ elsif scheduler_class == "false"
65
+ options.delete(:scheduler_class)
66
+ else
67
+ options[:scheduler_class] = scheduler_class
68
+ end
45
69
  end
46
70
 
47
- opts.on("--http_port HTTP_PORT", Integer, "HTTP port for the application (default: #{options[:http_port]})") do |hp|
48
- options[:http_port] = hp
71
+ opts.on("--preload [true, false, :bundle_group_name]", String, " Toggle preloading the application") do |preload|
72
+ if preload == "true"
73
+ options[:preload] = true
74
+ elsif preload == "false"
75
+ options[:preload] = false
76
+ else
77
+ # Not supported yet
78
+ end
49
79
  end
50
80
 
51
- opts.on("-c", "--cert_path CERT_PATH", String, "Path to the SSL certificate file") do |cp|
52
- options[:cert_path] = cp
81
+ opts.on("-b", "--bind BIND", String,
82
+ "Bind address (default: http://0.0.0.0:3000). You can specify this flag multiple times to bind to multiple addresses.") do |bind|
83
+ options[:binds] ||= []
84
+ options[:binds] << bind
85
+ end
86
+
87
+ opts.on("-c", "--cert_path CERT_PATH", String,
88
+ "Path to the SSL certificate file (must follow a --bind option). You can specify this flag multiple times.") do |cp|
89
+ raise OptionParser::InvalidOption, "--cert_path must follow a --bind" if options[:binds].empty?
90
+
91
+ require "uri"
92
+
93
+ # Modify the last bind entry to add/update the cert query parameter
94
+ uri = URI.parse("http://#{options[:binds].last}") # Ensure valid URI parsing
95
+ params = URI.decode_www_form(uri.query.to_s).to_h
96
+ params["cert"] = cp
97
+ query_string = params.map { |k, v| "#{k}=#{v}" }.join("&")
98
+ options[:binds][-1] = "#{uri.host}?#{query_string}"
53
99
  end
54
100
 
55
- opts.on("-k", "--key_path KEY_PATH", String, "Path to the SSL key file") do |kp|
56
- options[:key_path] = kp
101
+ opts.on("-k", "--key_path KEY_PATH", String,
102
+ "Path to the SSL key file (must follow a --bind option). You can specify this flag multiple times.") do |kp|
103
+ raise OptionParser::InvalidOption, "--key_path must follow a --bind" if options[:binds].empty?
104
+
105
+ require "uri"
106
+
107
+ # Modify the last bind entry to add/update the key query parameter
108
+ uri = URI.parse("http://#{options[:binds].last}") # Ensure valid URI parsing
109
+ params = URI.decode_www_form(uri.query.to_s).to_h
110
+ params["key"] = kp
111
+ query_string = params.map { |k, v| "#{k}=#{v}" }.join("&")
112
+ options[:binds][-1] = "#{uri.host}?#{query_string}"
57
113
  end
58
114
 
59
115
  opts.on("--shutdown_timeout SHUTDOWN_TIMEOUT", String,
@@ -61,24 +117,63 @@ OptionParser.new do |opts|
61
117
  options[:shutdown_timeout] = shutdown_timeout
62
118
  end
63
119
 
64
- opts.on("--script_name SCRIPT_NAME", String, "Script name to inject into Rack ENV") do |script_name|
65
- options[:script_name] = script_name
120
+
121
+ opts.on("--stream-body", TrueClass, "Stream body frames (default: false for best compatibility)") do |stream_body|
122
+ options[:stream_body] = stream_body
66
123
  end
67
124
 
68
- opts.on("--help", "Show this help message") do
125
+ opts.on("-h", "--help", "Show this help message") do
69
126
  puts opts
127
+ puts "COMMAND: "
128
+ COMMANDS.each do |command, description|
129
+ puts " #{command} - #{description}"
130
+ end
70
131
  exit
71
132
  end
72
- end.parse!
73
133
 
74
- # Parse the Rack application
75
- app, _ = Rack::Builder.parse_file("config.ru")
134
+ opts.on("--reexec PARAMS", String, "Reexec the server with the given parameters") do |params|
135
+ options[:reexec] = params
136
+ end
76
137
 
77
- puts "App is #{app}"
78
- # Make sure osprey is loaded, if not already loaded by the rack_app above.
79
- # Start the Osprey server
80
- require "itsi/server"
81
- Itsi::Server.new(
82
- app: app,
83
- **options
84
- ).start
138
+ opts.on("--listeners LISTENERS", String, "Listeners for reexec") do |listeners|
139
+ options[:listeners] = listeners
140
+ end
141
+
142
+ opts.on("--passfile PASSFILE", String, "Passfile") do |passfile|
143
+ options[:passfile] = passfile
144
+ end
145
+
146
+ opts.on("--algorithm ALGORITHM", String, "Algorithm for password hashing") do |algorithm|
147
+ options[:algorithm] = algorithm
148
+ end
149
+ end
150
+
151
+ if ENV['COMP_LINE'] || ARGV.include?('--completion')
152
+ puts COMMANDS.keys
153
+ exit
154
+ end
155
+
156
+ parser.parse!
157
+
158
+ case (command = ARGV.shift)
159
+ when *COMMANDS.keys
160
+ required_arity = Itsi::Server.method(command).parameters&.select{|c| c.first == :req }&.length&.succ || 2
161
+ case required_arity
162
+ when 1 then Itsi::Server.send(command)
163
+ when 2 then Itsi::Server.send(command, options)
164
+ else
165
+ if ARGV.length != required_arity - 2
166
+ puts "Command #{command} requires #{required_arity - 2} subcommands. "
167
+ exit(0)
168
+ end
169
+ Itsi::Server.send(command, options, *ARGV)
170
+ end
171
+ when nil
172
+ Itsi::Server.start(options)
173
+ else
174
+ puts "Invalid command #{command}.\n"
175
+ puts "COMMAND: "
176
+ COMMANDS.each do |command, description|
177
+ puts " #{command} - #{description}"
178
+ end
179
+ end
@@ -7,3 +7,6 @@ edition = "2024"
7
7
  thiserror = "2.0.11"
8
8
  magnus = { version = "0.7.1" }
9
9
  rcgen = "0.13.2"
10
+ nix = "0.29.0"
11
+ httparse = "1.10.1"
12
+ anyhow = "1.0.97"
@@ -1,49 +1,123 @@
1
+ pub use anyhow::Context;
2
+ use magnus::Error as MagnusError;
3
+ use magnus::{
4
+ error::ErrorType,
5
+ exception::{self, arg_error, standard_error},
6
+ };
1
7
  use thiserror::Error;
2
8
 
9
+ pub static CLIENT_CONNECTION_CLOSED: &str = "Client disconnected";
3
10
  pub type Result<T> = std::result::Result<T, ItsiError>;
4
11
 
5
12
  #[derive(Error, Debug)]
6
13
  pub enum ItsiError {
7
- #[error("Invalid input")]
14
+ #[error("Invalid input: {0}")]
8
15
  InvalidInput(String),
9
- #[error("Internal server error")]
10
- InternalServerError,
11
- #[error("Unsupported protocol")]
16
+
17
+ #[error("Internal server error: {0}")]
18
+ InternalServerError(String),
19
+
20
+ #[error("Unsupported protocol: {0}")]
12
21
  UnsupportedProtocol(String),
13
- #[error("Argument error")]
22
+
23
+ #[error("Argument error: {0}")]
14
24
  ArgumentError(String),
15
- #[error("Jump")]
25
+
26
+ #[error("Client Connection Closed")]
27
+ ClientConnectionClosed,
28
+
29
+ #[error("Internal Error")]
30
+ InternalError(String),
31
+
32
+ #[error(transparent)]
33
+ Io(#[from] std::io::Error),
34
+
35
+ #[error(transparent)]
36
+ Rcgen(#[from] rcgen::Error),
37
+
38
+ #[error(transparent)]
39
+ HttpParse(#[from] httparse::Error),
40
+
41
+ #[error(transparent)]
42
+ NixErrno(#[from] nix::errno::Errno),
43
+
44
+ #[error(transparent)]
45
+ Nul(#[from] std::ffi::NulError),
46
+
47
+ #[error("Jump: {0}")]
16
48
  Jump(String),
49
+
50
+ #[error("Break")]
51
+ Break,
52
+
53
+ #[error("Pass")]
54
+ Pass,
55
+
56
+ #[error(transparent)]
57
+ Anyhow(#[from] anyhow::Error),
17
58
  }
18
59
 
19
- impl From<ItsiError> for magnus::Error {
20
- fn from(err: ItsiError) -> Self {
21
- magnus::Error::new(magnus::exception::runtime_error(), err.to_string())
60
+ impl From<magnus::Error> for ItsiError {
61
+ fn from(err: magnus::Error) -> Self {
62
+ match err.error_type() {
63
+ ErrorType::Jump(tag) => ItsiError::Jump(tag.to_string()),
64
+ ErrorType::Error(_exception_class, cow) => ItsiError::ArgumentError(cow.to_string()),
65
+ ErrorType::Exception(exception) => ItsiError::ArgumentError(exception.to_string()),
66
+ }
22
67
  }
23
68
  }
24
69
 
25
- impl From<std::io::Error> for ItsiError {
26
- fn from(err: std::io::Error) -> Self {
27
- ItsiError::ArgumentError(err.to_string())
70
+ pub trait IntoMagnusError {
71
+ fn into_magnus_error(self) -> MagnusError;
72
+ }
73
+
74
+ impl<T: std::error::Error> IntoMagnusError for T {
75
+ fn into_magnus_error(self) -> MagnusError {
76
+ MagnusError::new(standard_error(), self.to_string())
28
77
  }
29
78
  }
30
79
 
31
- impl From<rcgen::Error> for ItsiError {
32
- fn from(err: rcgen::Error) -> Self {
33
- ItsiError::ArgumentError(err.to_string())
80
+ impl From<&str> for ItsiError {
81
+ fn from(s: &str) -> Self {
82
+ ItsiError::InternalError(s.to_owned())
34
83
  }
35
84
  }
36
85
 
37
- impl From<magnus::Error> for ItsiError {
38
- fn from(err: magnus::Error) -> Self {
39
- match err.error_type() {
40
- magnus::error::ErrorType::Jump(tag) => ItsiError::Jump(tag.to_string()),
41
- magnus::error::ErrorType::Error(_exception_class, cow) => {
42
- ItsiError::ArgumentError(cow.to_string())
43
- }
44
- magnus::error::ErrorType::Exception(exception) => {
45
- ItsiError::ArgumentError(exception.to_string())
86
+ impl From<String> for ItsiError {
87
+ fn from(s: String) -> Self {
88
+ ItsiError::InternalError(s)
89
+ }
90
+ }
91
+
92
+ impl From<ItsiError> for magnus::Error {
93
+ fn from(err: ItsiError) -> Self {
94
+ match err {
95
+ ItsiError::InvalidInput(msg) => magnus::Error::new(arg_error(), msg),
96
+ ItsiError::InternalServerError(msg) => magnus::Error::new(standard_error(), msg),
97
+ ItsiError::InternalError(msg) => magnus::Error::new(standard_error(), msg),
98
+ ItsiError::UnsupportedProtocol(msg) => magnus::Error::new(arg_error(), msg),
99
+ ItsiError::ArgumentError(msg) => magnus::Error::new(arg_error(), msg),
100
+ ItsiError::Jump(msg) => magnus::Error::new(exception::local_jump_error(), msg),
101
+ ItsiError::ClientConnectionClosed => {
102
+ magnus::Error::new(exception::eof_error(), CLIENT_CONNECTION_CLOSED)
46
103
  }
104
+ ItsiError::Break => magnus::Error::new(exception::interrupt(), "Break"),
105
+ ItsiError::Pass => magnus::Error::new(exception::interrupt(), "Pass"),
106
+ ItsiError::Io(err) => err.into_magnus_error(),
107
+ ItsiError::Rcgen(err) => err.into_magnus_error(),
108
+ ItsiError::HttpParse(err) => err.into_magnus_error(),
109
+ ItsiError::NixErrno(err) => err.into_magnus_error(),
110
+ ItsiError::Nul(err) => err.into_magnus_error(),
111
+ ItsiError::Anyhow(err) => err.into_magnus_error(),
47
112
  }
48
113
  }
49
114
  }
115
+
116
+ impl ItsiError {
117
+ pub fn new(error: impl Send + Sync + 'static + std::fmt::Display) -> Self {
118
+ ItsiError::InternalError(format!("{}", error))
119
+ }
120
+ }
121
+
122
+ unsafe impl Send for ItsiError {}
123
+ unsafe impl Sync for ItsiError {}