itsi 0.1.20 → 0.2.2

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 (319) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -8
  3. data/Cargo.lock +2 -2
  4. data/LICENSE.txt +698 -0
  5. data/README.md +15 -4
  6. data/Rakefile +9 -5
  7. data/crates/itsi_acme/.gitignore +4 -0
  8. data/crates/itsi_acme/Cargo.toml +86 -0
  9. data/crates/itsi_acme/LICENSE-APACHE +201 -0
  10. data/crates/itsi_acme/LICENSE-MIT +23 -0
  11. data/crates/itsi_acme/README.md +9 -0
  12. data/crates/itsi_acme/examples/high_level.rs +63 -0
  13. data/crates/itsi_acme/examples/high_level_warp.rs +52 -0
  14. data/crates/itsi_acme/examples/low_level.rs +87 -0
  15. data/crates/itsi_acme/examples/low_level_axum.rs +66 -0
  16. data/crates/itsi_acme/src/acceptor.rs +81 -0
  17. data/crates/itsi_acme/src/acme.rs +354 -0
  18. data/crates/itsi_acme/src/axum.rs +86 -0
  19. data/crates/itsi_acme/src/cache.rs +39 -0
  20. data/crates/itsi_acme/src/caches/boxed.rs +80 -0
  21. data/crates/itsi_acme/src/caches/composite.rs +69 -0
  22. data/crates/itsi_acme/src/caches/dir.rs +106 -0
  23. data/crates/itsi_acme/src/caches/mod.rs +11 -0
  24. data/crates/itsi_acme/src/caches/no.rs +78 -0
  25. data/crates/itsi_acme/src/caches/test.rs +136 -0
  26. data/crates/itsi_acme/src/config.rs +172 -0
  27. data/crates/itsi_acme/src/https_helper.rs +69 -0
  28. data/crates/itsi_acme/src/incoming.rs +142 -0
  29. data/crates/itsi_acme/src/jose.rs +161 -0
  30. data/crates/itsi_acme/src/lib.rs +142 -0
  31. data/crates/itsi_acme/src/resolver.rs +59 -0
  32. data/crates/itsi_acme/src/state.rs +424 -0
  33. data/crates/itsi_rb_helpers/src/lib.rs +4 -3
  34. data/crates/itsi_scheduler/Cargo.toml +1 -1
  35. data/crates/itsi_scheduler/src/itsi_scheduler.rs +8 -2
  36. data/crates/itsi_scheduler/src/lib.rs +1 -0
  37. data/crates/itsi_server/Cargo.toml +1 -1
  38. data/crates/itsi_server/src/lib.rs +2 -1
  39. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +18 -1
  40. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +11 -3
  41. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +122 -63
  42. data/crates/itsi_server/src/ruby_types/itsi_server.rs +2 -0
  43. data/crates/itsi_server/src/server/binds/bind.rs +3 -0
  44. data/crates/itsi_server/src/server/binds/listener.rs +12 -5
  45. data/crates/itsi_server/src/server/binds/tls.rs +13 -5
  46. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +12 -5
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +8 -1
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +9 -1
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +48 -43
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +11 -2
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +39 -12
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +36 -27
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +25 -11
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +12 -3
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +74 -72
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +15 -1
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +11 -8
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +19 -11
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +5 -5
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +2 -2
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +11 -5
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +17 -20
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +19 -8
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +16 -37
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +22 -12
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +26 -11
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +7 -1
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +14 -4
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +19 -0
  70. data/crates/itsi_server/src/server/middleware_stack/mod.rs +49 -13
  71. data/crates/itsi_server/src/server/mod.rs +1 -0
  72. data/crates/itsi_server/src/server/redirect_type.rs +26 -0
  73. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +22 -16
  74. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +49 -12
  75. data/crates/itsi_server/src/server/signal.rs +1 -0
  76. data/crates/itsi_server/src/server/size_limited_incoming.rs +6 -0
  77. data/crates/itsi_server/src/server/thread_worker.rs +5 -1
  78. data/crates/itsi_server/src/services/itsi_http_service.rs +20 -2
  79. data/crates/itsi_server/src/services/rate_limiter.rs +15 -4
  80. data/crates/itsi_server/src/services/static_file_server.rs +33 -19
  81. data/crates/itsi_tracing/src/lib.rs +42 -22
  82. data/docs/content/_index.md +1 -2
  83. data/docs/content/acknowledgements/_index.md +5 -2
  84. data/docs/content/configuration/_index.md +8 -5
  85. data/docs/content/contact/_index.md +8 -1
  86. data/docs/content/faqs/_index.md +5 -3
  87. data/docs/content/features/_index.md +56 -50
  88. data/docs/content/getting_started/_index.md +8 -5
  89. data/docs/content/getting_started/local_development.md +68 -8
  90. data/docs/content/getting_started/logging.md +16 -9
  91. data/docs/content/getting_started/running_itsi_in_production.md +5 -3
  92. data/docs/content/getting_started/signals.md +38 -0
  93. data/docs/content/itsi_scheduler/_index.md +8 -7
  94. data/docs/content/utilities/_index.md +13 -0
  95. data/docs/content/utilities/config_file_testing.md +17 -0
  96. data/docs/content/utilities/passfile_generator.md +41 -0
  97. data/docs/content/utilities/route_testing.md +27 -0
  98. data/docs/content/utilities/secrets_management.md +30 -0
  99. data/docs/hugo.yaml +1 -1
  100. data/fairytale.txt +3 -4
  101. data/gems/scheduler/Cargo.lock +1 -1
  102. data/gems/scheduler/README.md +4 -5
  103. data/gems/scheduler/Rakefile +0 -4
  104. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  105. data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
  106. data/gems/scheduler/test/test_active_record.rb +12 -7
  107. data/gems/server/Cargo.lock +1 -1
  108. data/gems/server/Rakefile +0 -4
  109. data/gems/server/exe/itsi +13 -2
  110. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
  111. data/gems/server/lib/itsi/http_request.rb +40 -9
  112. data/gems/server/lib/itsi/http_response.rb +2 -1
  113. data/gems/server/lib/itsi/passfile.rb +0 -1
  114. data/gems/server/lib/itsi/server/config/config_helpers.rb +20 -8
  115. data/gems/server/lib/itsi/server/config/dsl.rb +20 -435
  116. data/gems/server/lib/itsi/server/config/known_paths.rb +4 -1
  117. data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
  118. data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
  119. data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
  120. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
  121. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
  122. data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
  123. data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
  124. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
  125. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
  126. data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
  127. data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
  128. data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
  129. data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
  130. data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
  131. data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
  132. data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
  133. data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
  134. data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
  135. data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
  136. data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
  137. data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
  138. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
  139. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
  140. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
  141. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
  142. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
  143. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
  144. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
  145. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
  146. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
  147. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
  148. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
  149. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
  150. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
  151. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
  152. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
  153. data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
  154. data/gems/server/lib/itsi/server/config/middleware/error_response.md +61 -0
  155. data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
  156. data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
  157. data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
  158. data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
  159. data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
  160. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
  161. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
  162. data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
  163. data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
  164. data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
  165. data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
  166. data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
  167. data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
  168. data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
  169. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
  170. data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
  171. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
  172. data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
  173. data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
  174. data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
  175. data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
  176. data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
  177. data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
  178. data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
  179. data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
  180. data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
  181. data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
  182. data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
  183. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +73 -0
  184. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
  185. data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
  186. data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
  187. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
  188. data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
  189. data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
  190. data/gems/server/lib/itsi/server/config/option.rb +5 -0
  191. data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
  192. data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
  193. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
  194. data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
  195. data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
  196. data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
  197. data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
  198. data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
  199. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
  200. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
  201. data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
  202. data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
  203. data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
  204. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
  205. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
  206. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
  207. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
  208. data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
  209. data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
  210. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
  211. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
  212. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
  213. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
  214. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
  215. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
  216. data/gems/server/lib/itsi/server/config/options/include.md +20 -0
  217. data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
  218. data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
  219. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
  220. data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
  221. data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
  222. data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
  223. data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
  224. data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
  225. data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
  226. data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
  227. data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
  228. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
  229. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
  230. data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
  231. data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
  232. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
  233. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
  234. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
  235. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
  236. data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
  237. data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
  238. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
  239. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
  240. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
  241. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
  242. data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
  243. data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
  244. data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
  245. data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
  246. data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
  247. data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
  248. data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
  249. data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
  250. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
  251. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
  252. data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
  253. data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
  254. data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
  255. data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
  256. data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
  257. data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
  258. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
  259. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
  260. data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
  261. data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
  262. data/gems/server/lib/itsi/server/config/typed_struct.rb +59 -20
  263. data/gems/server/lib/itsi/server/config.rb +77 -48
  264. data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
  265. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +1 -1
  266. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +11 -4
  267. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -3
  268. data/gems/server/lib/itsi/server/route_tester.rb +58 -8
  269. data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
  270. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
  271. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +5 -4
  272. data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
  273. data/gems/server/lib/itsi/server/version.rb +1 -1
  274. data/gems/server/lib/itsi/server.rb +98 -14
  275. data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
  276. data/gems/server/test/helpers/test_helper.rb +89 -29
  277. data/gems/server/test/middleware/allow_list.rb +128 -0
  278. data/gems/server/test/middleware/auth_api_key.rb +141 -0
  279. data/gems/server/test/middleware/auth_basic.rb +91 -0
  280. data/gems/server/test/middleware/auth_jwt.rb +214 -0
  281. data/gems/server/test/middleware/cache_control.rb +82 -0
  282. data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
  283. data/gems/server/test/middleware/compression.rb +89 -0
  284. data/gems/server/test/middleware/cors.rb +113 -0
  285. data/gems/server/test/middleware/csp.rb +62 -0
  286. data/gems/server/test/middleware/deny_list.rb +131 -0
  287. data/gems/server/test/middleware/endpoint.rb +300 -0
  288. data/gems/server/test/middleware/etag.rb +75 -0
  289. data/gems/server/test/middleware/grpc/grpc.rb +158 -0
  290. data/gems/server/test/middleware/grpc/test_service.proto +32 -0
  291. data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
  292. data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
  293. data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
  294. data/gems/server/test/middleware/header_interpolation.rb +35 -0
  295. data/gems/server/test/middleware/intrusion_protection.rb +259 -0
  296. data/gems/server/test/middleware/location.rb +220 -0
  297. data/gems/server/test/middleware/max_body.rb +20 -0
  298. data/gems/server/test/middleware/proxy.rb +415 -0
  299. data/gems/server/test/middleware/rate_limit.rb +211 -0
  300. data/gems/server/test/middleware/redirect.rb +85 -0
  301. data/gems/server/test/middleware/request_headers.rb +50 -0
  302. data/gems/server/test/middleware/response_headers.rb +50 -0
  303. data/gems/server/test/middleware/static_assets.rb +374 -0
  304. data/gems/server/test/middleware/static_response.rb +56 -0
  305. data/gems/server/test/middleware/string_rewrite.rb +112 -0
  306. data/gems/server/test/options/bind.rb +47 -0
  307. data/gems/server/test/options/header_read_timeout.rb +23 -0
  308. data/gems/server/test/options/test_request_timeout.rb +16 -0
  309. data/gems/server/test/options/test_workers.rb +2 -4
  310. data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
  311. data/grpc_test/Itsi.rb +11 -0
  312. data/grpc_test/echo.proto +14 -0
  313. data/grpc_test/echo_pb.rb +16 -0
  314. data/grpc_test/echo_service_impl.rb +8 -0
  315. data/grpc_test/echo_services_pb.rb +22 -0
  316. data/lib/itsi/version.rb +1 -1
  317. data/tasks.txt +15 -72
  318. metadata +210 -7
  319. data/gems/server/lib/itsi/server/default_config/Itsi-rackup.rb +0 -119
@@ -0,0 +1,424 @@
1
+ use std::convert::Infallible;
2
+ use std::fmt::Debug;
3
+ use std::future::Future;
4
+ use std::pin::Pin;
5
+ use std::sync::Arc;
6
+ use std::task::{Context, Poll};
7
+ use std::time::Duration;
8
+
9
+ use chrono::{DateTime, TimeZone, Utc};
10
+ use futures::future::try_join_all;
11
+ use futures::{ready, FutureExt, Stream};
12
+ use rcgen::{CertificateParams, DistinguishedName, Error as RcgenError, PKCS_ECDSA_P256_SHA256};
13
+ use rustls::crypto::ring::sign::any_ecdsa_type;
14
+ use rustls::pki_types::{CertificateDer as RustlsCertificate, PrivateKeyDer, PrivatePkcs8KeyDer};
15
+ use rustls::sign::CertifiedKey;
16
+ use thiserror::Error;
17
+ use tokio::io::{AsyncRead, AsyncWrite};
18
+ use tokio::time::Sleep;
19
+ use x509_parser::parse_x509_certificate;
20
+
21
+ use crate::acceptor::AcmeAcceptor;
22
+ use crate::acme::{
23
+ Account, AcmeError, Auth, AuthStatus, Directory, Identifier, Order, OrderStatus,
24
+ };
25
+ use crate::{AcmeConfig, Incoming, ResolvesServerCertAcme};
26
+
27
+ type Timer = std::pin::Pin<Box<Sleep>>;
28
+ type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
29
+
30
+ pub fn after(d: std::time::Duration) -> Timer {
31
+ Box::pin(tokio::time::sleep(d))
32
+ }
33
+
34
+ #[allow(clippy::type_complexity)]
35
+ pub struct AcmeState<EC: Debug = Infallible, EA: Debug = EC> {
36
+ config: Arc<AcmeConfig<EC, EA>>,
37
+ resolver: Arc<ResolvesServerCertAcme>,
38
+ account_key: Option<Vec<u8>>,
39
+
40
+ early_action: Option<BoxFuture<Event<EC, EA>>>,
41
+ load_cert: Option<BoxFuture<Result<Option<Vec<u8>>, EC>>>,
42
+ load_account: Option<BoxFuture<Result<Option<Vec<u8>>, EA>>>,
43
+ order: Option<BoxFuture<Result<Vec<u8>, OrderError>>>,
44
+ backoff_cnt: usize,
45
+ wait: Option<Timer>,
46
+ }
47
+
48
+ pub type Event<EC, EA> = Result<EventOk, EventError<EC, EA>>;
49
+
50
+ #[derive(Debug)]
51
+ pub enum EventOk {
52
+ DeployedCachedCert,
53
+ DeployedNewCert,
54
+ CertCacheStore,
55
+ AccountCacheStore,
56
+ }
57
+
58
+ #[derive(Error, Debug)]
59
+ pub enum EventError<EC: Debug, EA: Debug> {
60
+ #[error("cert cache load: {0}")]
61
+ CertCacheLoad(EC),
62
+ #[error("account cache load: {0}")]
63
+ AccountCacheLoad(EA),
64
+ #[error("cert cache store: {0}")]
65
+ CertCacheStore(EC),
66
+ #[error("account cache store: {0}")]
67
+ AccountCacheStore(EA),
68
+ #[error("cached cert parse: {0}")]
69
+ CachedCertParse(CertParseError),
70
+ #[error("order: {0}")]
71
+ Order(OrderError),
72
+ #[error("new cert parse: {0}")]
73
+ NewCertParse(CertParseError),
74
+ }
75
+
76
+ #[derive(Error, Debug)]
77
+ pub enum OrderError {
78
+ #[error("acme error: {0}")]
79
+ Acme(#[from] AcmeError),
80
+ #[error("certificate generation error: {0}")]
81
+ Rcgen(#[from] RcgenError),
82
+ #[error("bad order object: {0:?}")]
83
+ BadOrder(Order),
84
+ #[error("bad auth object: {0:?}")]
85
+ BadAuth(Auth),
86
+ #[error("authorization for {0} failed too many times")]
87
+ TooManyAttemptsAuth(String),
88
+ #[error("order status stayed on processing too long")]
89
+ ProcessingTimeout(Order),
90
+ }
91
+
92
+ #[derive(Error, Debug)]
93
+ pub enum CertParseError {
94
+ #[error("X509 parsing error: {0}")]
95
+ X509(#[from] x509_parser::nom::Err<x509_parser::error::X509Error>),
96
+ #[error("expected 2 or more pem, got: {0}")]
97
+ Pem(#[from] pem::PemError),
98
+ #[error("expected 2 or more pem, got: {0}")]
99
+ TooFewPem(usize),
100
+ #[error("unsupported private key type")]
101
+ InvalidPrivateKey,
102
+ }
103
+
104
+ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
105
+ pub fn incoming<
106
+ TCP: AsyncRead + AsyncWrite + Unpin,
107
+ ETCP,
108
+ ITCP: Stream<Item = Result<TCP, ETCP>> + Unpin,
109
+ >(
110
+ self,
111
+ tcp_incoming: ITCP,
112
+ alpn_protocols: Vec<Vec<u8>>,
113
+ ) -> Incoming<TCP, ETCP, ITCP, EC, EA> {
114
+ let acceptor = self.acceptor();
115
+ Incoming::new(tcp_incoming, self, acceptor, alpn_protocols)
116
+ }
117
+ pub fn acceptor(&self) -> AcmeAcceptor {
118
+ AcmeAcceptor::new(self.resolver())
119
+ }
120
+
121
+ #[cfg(feature = "axum")]
122
+ pub fn axum_acceptor(
123
+ &self,
124
+ rustls_config: Arc<rustls::ServerConfig>,
125
+ ) -> crate::axum::AxumAcceptor {
126
+ crate::axum::AxumAcceptor::new(self.acceptor(), rustls_config)
127
+ }
128
+ pub fn resolver(&self) -> Arc<ResolvesServerCertAcme> {
129
+ self.resolver.clone()
130
+ }
131
+ pub fn new(config: AcmeConfig<EC, EA>) -> Self {
132
+ let config = Arc::new(config);
133
+ Self {
134
+ config: config.clone(),
135
+ resolver: ResolvesServerCertAcme::new(),
136
+ account_key: None,
137
+ early_action: None,
138
+ load_cert: Some(Box::pin({
139
+ let config = config.clone();
140
+ async move {
141
+ config
142
+ .cache
143
+ .load_cert(&config.domains, &config.directory_url)
144
+ .await
145
+ }
146
+ })),
147
+ load_account: Some(Box::pin({
148
+ let config = config;
149
+ async move {
150
+ config
151
+ .cache
152
+ .load_account(&config.contact, &config.directory_url)
153
+ .await
154
+ }
155
+ })),
156
+ order: None,
157
+ backoff_cnt: 0,
158
+ wait: None,
159
+ }
160
+ }
161
+ fn parse_cert(pem: &[u8]) -> Result<(CertifiedKey, [DateTime<Utc>; 2]), CertParseError> {
162
+ let mut pems = pem::parse_many(pem)?;
163
+ if pems.len() < 2 {
164
+ return Err(CertParseError::TooFewPem(pems.len()));
165
+ }
166
+ let pk_bytes = pems.remove(0).into_contents();
167
+ let pk_der: PrivatePkcs8KeyDer = pk_bytes.into();
168
+ let pk: PrivateKeyDer = pk_der.into();
169
+ let pk = match any_ecdsa_type(&pk) {
170
+ Ok(pk) => pk,
171
+ Err(_) => return Err(CertParseError::InvalidPrivateKey),
172
+ };
173
+ let cert_chain: Vec<RustlsCertificate> =
174
+ pems.into_iter().map(|p| p.into_contents().into()).collect();
175
+ let validity = match parse_x509_certificate(cert_chain[0].as_ref()) {
176
+ Ok((_, cert)) => {
177
+ let validity = cert.validity();
178
+ [validity.not_before, validity.not_after]
179
+ .map(|t| Utc.timestamp_opt(t.timestamp(), 0).earliest().unwrap())
180
+ }
181
+ Err(err) => return Err(CertParseError::X509(err)),
182
+ };
183
+ let cert = CertifiedKey::new(cert_chain, pk);
184
+ Ok((cert, validity))
185
+ }
186
+
187
+ #[allow(clippy::result_large_err)]
188
+ fn process_cert(&mut self, pem: Vec<u8>, cached: bool) -> Event<EC, EA> {
189
+ let (cert, validity) = match (Self::parse_cert(&pem), cached) {
190
+ (Ok(r), _) => r,
191
+ (Err(err), cached) => {
192
+ return match cached {
193
+ true => Err(EventError::CachedCertParse(err)),
194
+ false => Err(EventError::NewCertParse(err)),
195
+ }
196
+ }
197
+ };
198
+ self.resolver.set_cert(Arc::new(cert));
199
+ let wait_duration = (validity[1] - (validity[1] - validity[0]) / 3 - Utc::now())
200
+ .max(chrono::Duration::zero())
201
+ .to_std()
202
+ .unwrap_or_default();
203
+ self.wait = Some(after(wait_duration));
204
+ if cached {
205
+ return Ok(EventOk::DeployedCachedCert);
206
+ }
207
+ let config = self.config.clone();
208
+ self.early_action = Some(Box::pin(async move {
209
+ match config
210
+ .cache
211
+ .store_cert(&config.domains, &config.directory_url, &pem)
212
+ .await
213
+ {
214
+ Ok(()) => Ok(EventOk::CertCacheStore),
215
+ Err(err) => Err(EventError::CertCacheStore(err)),
216
+ }
217
+ }));
218
+ Event::Ok(EventOk::DeployedNewCert)
219
+ }
220
+ async fn order(
221
+ config: Arc<AcmeConfig<EC, EA>>,
222
+ resolver: Arc<ResolvesServerCertAcme>,
223
+ key_pair: Vec<u8>,
224
+ ) -> Result<Vec<u8>, OrderError> {
225
+ let directory = Directory::discover(&config.client_config, &config.directory_url).await?;
226
+ let account = Account::create_with_keypair(
227
+ &config.client_config,
228
+ directory,
229
+ &config.contact,
230
+ &key_pair,
231
+ &config.eab,
232
+ )
233
+ .await?;
234
+
235
+ let mut params = CertificateParams::new(config.domains.clone())?;
236
+ params.distinguished_name = DistinguishedName::new();
237
+ let key_pair = rcgen::KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?;
238
+
239
+ let (order_url, mut order) = account
240
+ .new_order(&config.client_config, config.domains.clone())
241
+ .await?;
242
+ loop {
243
+ match order.status {
244
+ OrderStatus::Pending => {
245
+ let auth_futures = order
246
+ .authorizations
247
+ .iter()
248
+ .map(|url| Self::authorize(&config, &resolver, &account, url));
249
+ try_join_all(auth_futures).await?;
250
+ log::info!("completed all authorizations");
251
+ order = account.order(&config.client_config, &order_url).await?;
252
+ }
253
+ OrderStatus::Processing => {
254
+ for i in 0u64..10 {
255
+ log::info!("order processing");
256
+ after(Duration::from_secs(1u64 << i)).await;
257
+ order = account.order(&config.client_config, &order_url).await?;
258
+ if order.status != OrderStatus::Processing {
259
+ break;
260
+ }
261
+ }
262
+ if order.status == OrderStatus::Processing {
263
+ return Err(OrderError::ProcessingTimeout(order));
264
+ }
265
+ }
266
+ OrderStatus::Ready => {
267
+ log::info!("sending csr");
268
+ let csr = params.serialize_request(&key_pair)?;
269
+ order = account
270
+ .finalize(&config.client_config, order.finalize, csr.der().to_vec())
271
+ .await?
272
+ }
273
+ OrderStatus::Valid { certificate } => {
274
+ log::info!("download certificate");
275
+ let pem = [
276
+ &key_pair.serialize_pem(),
277
+ "\n",
278
+ &account
279
+ .certificate(&config.client_config, certificate)
280
+ .await?,
281
+ ]
282
+ .concat();
283
+ return Ok(pem.into_bytes());
284
+ }
285
+ OrderStatus::Invalid => return Err(OrderError::BadOrder(order)),
286
+ }
287
+ }
288
+ }
289
+ async fn authorize(
290
+ config: &AcmeConfig<EC, EA>,
291
+ resolver: &ResolvesServerCertAcme,
292
+ account: &Account,
293
+ url: &String,
294
+ ) -> Result<(), OrderError> {
295
+ let auth = account.auth(&config.client_config, url).await?;
296
+ let (domain, challenge_url) = match auth.status {
297
+ AuthStatus::Pending => {
298
+ let Identifier::Dns(domain) = auth.identifier;
299
+ log::info!("trigger challenge for {}", &domain);
300
+ let (challenge, auth_key) =
301
+ account.tls_alpn_01(&auth.challenges, domain.clone())?;
302
+ resolver.set_auth_key(domain.clone(), Arc::new(auth_key));
303
+ account
304
+ .challenge(&config.client_config, &challenge.url)
305
+ .await?;
306
+ (domain, challenge.url.clone())
307
+ }
308
+ AuthStatus::Valid => return Ok(()),
309
+ _ => return Err(OrderError::BadAuth(auth)),
310
+ };
311
+ for i in 0u64..5 {
312
+ after(Duration::from_secs(1u64 << i)).await;
313
+ let auth = account.auth(&config.client_config, url).await?;
314
+ match auth.status {
315
+ AuthStatus::Pending => {
316
+ log::info!("authorization for {} still pending", &domain);
317
+ account
318
+ .challenge(&config.client_config, &challenge_url)
319
+ .await?
320
+ }
321
+ AuthStatus::Valid => return Ok(()),
322
+ _ => return Err(OrderError::BadAuth(auth)),
323
+ }
324
+ }
325
+ Err(OrderError::TooManyAttemptsAuth(domain))
326
+ }
327
+ fn poll_next_infinite(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Event<EC, EA>> {
328
+ loop {
329
+ // queued early action
330
+ if let Some(early_action) = &mut self.early_action {
331
+ let result = ready!(early_action.poll_unpin(cx));
332
+ self.early_action.take();
333
+ return Poll::Ready(result);
334
+ }
335
+
336
+ // sleep
337
+ if let Some(timer) = &mut self.wait {
338
+ ready!(timer.poll_unpin(cx));
339
+ self.wait.take();
340
+ }
341
+
342
+ // load from cert cache
343
+ if let Some(load_cert) = &mut self.load_cert {
344
+ let result = ready!(load_cert.poll_unpin(cx));
345
+ self.load_cert.take();
346
+ match result {
347
+ Ok(Some(pem)) => {
348
+ return Poll::Ready(Self::process_cert(self.get_mut(), pem, true));
349
+ }
350
+ Ok(None) => {}
351
+ Err(err) => return Poll::Ready(Err(EventError::CertCacheLoad(err))),
352
+ }
353
+ }
354
+
355
+ // load from account cache
356
+ if let Some(load_account) = &mut self.load_account {
357
+ let result = ready!(load_account.poll_unpin(cx));
358
+ self.load_account.take();
359
+ match result {
360
+ Ok(Some(key_pair)) => self.account_key = Some(key_pair),
361
+ Ok(None) => {}
362
+ Err(err) => return Poll::Ready(Err(EventError::AccountCacheLoad(err))),
363
+ }
364
+ }
365
+
366
+ // execute order
367
+ if let Some(order) = &mut self.order {
368
+ let result = ready!(order.poll_unpin(cx));
369
+ self.order.take();
370
+ match result {
371
+ Ok(pem) => {
372
+ self.backoff_cnt = 0;
373
+ return Poll::Ready(Self::process_cert(self.get_mut(), pem, false));
374
+ }
375
+ Err(err) => {
376
+ // TODO: replace key on some errors or high backoff_cnt?
377
+ self.wait = Some(after(Duration::from_secs(1 << self.backoff_cnt)));
378
+ self.backoff_cnt = (self.backoff_cnt + 1).min(16);
379
+ return Poll::Ready(Err(EventError::Order(err)));
380
+ }
381
+ }
382
+ }
383
+
384
+ // schedule order
385
+ let account_key = match &self.account_key {
386
+ None => {
387
+ let account_key = Account::generate_key_pair();
388
+ self.account_key = Some(account_key.clone());
389
+ let config = self.config.clone();
390
+ let account_key_clone = account_key.clone();
391
+ self.early_action = Some(Box::pin(async move {
392
+ match config
393
+ .cache
394
+ .store_account(
395
+ &config.contact,
396
+ &config.directory_url,
397
+ &account_key_clone,
398
+ )
399
+ .await
400
+ {
401
+ Ok(()) => Ok(EventOk::AccountCacheStore),
402
+ Err(err) => Err(EventError::AccountCacheStore(err)),
403
+ }
404
+ }));
405
+ account_key
406
+ }
407
+ Some(account_key) => account_key.clone(),
408
+ };
409
+ let config = self.config.clone();
410
+ let resolver = self.resolver.clone();
411
+ self.order = Some(Box::pin({
412
+ Self::order(config.clone(), resolver.clone(), account_key)
413
+ }));
414
+ }
415
+ }
416
+ }
417
+
418
+ impl<EC: 'static + Debug, EA: 'static + Debug> Stream for AcmeState<EC, EA> {
419
+ type Item = Event<EC, EA>;
420
+
421
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
422
+ Poll::Ready(Some(ready!(self.poll_next_infinite(cx))))
423
+ }
424
+ }
@@ -25,7 +25,7 @@ pub fn schedule_thread() {
25
25
  rb_thread_schedule();
26
26
  };
27
27
  }
28
- pub fn create_ruby_thread<F>(f: F) -> Thread
28
+ pub fn create_ruby_thread<F>(f: F) -> Option<Thread>
29
29
  where
30
30
  F: FnOnce() + Send + 'static,
31
31
  {
@@ -51,7 +51,7 @@ where
51
51
  let thread = rb_thread_create(Some(trampoline::<F>), ptr);
52
52
  rb_thread_wakeup(thread);
53
53
  rb_thread_schedule();
54
- Thread::from_value(Value::from_raw(thread)).unwrap()
54
+ Thread::from_value(Value::from_raw(thread))
55
55
  }
56
56
  }
57
57
 
@@ -127,7 +127,8 @@ pub fn fork(after_fork: Option<HeapValue<Proc>>) -> Option<i32> {
127
127
  let fork_result = ruby
128
128
  .module_kernel()
129
129
  .funcall::<_, _, Option<i32>>(*ID_FORK, ())
130
- .unwrap();
130
+ .ok()
131
+ .flatten();
131
132
  if fork_result.is_none() {
132
133
  if let Some(proc) = after_fork {
133
134
  call_proc_and_log_errors(proc)
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-scheduler"
3
- version = "0.1.0"
3
+ version = "0.2.2"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -115,6 +115,11 @@ impl ItsiScheduler {
115
115
  self.timers.lock().push(timer_entry);
116
116
  }
117
117
  }
118
+
119
+ pub fn clear_timer(&self, token: usize) {
120
+ self.timers.lock().retain(|timer| timer.token.0 != token);
121
+ }
122
+
118
123
  pub fn has_pending_io(&self) -> bool {
119
124
  !self.timers.lock().is_empty() || !self.io_waiters.lock().is_empty()
120
125
  }
@@ -148,8 +153,9 @@ impl ItsiScheduler {
148
153
  let mut events = self.events.lock();
149
154
  {
150
155
  let mut poll = self.poll.lock();
151
- poll.poll(&mut events, timeout)
152
- .map_err(|e| ItsiError::ArgumentError(format!("poll error: {}", e)))?;
156
+ if let Err(_err) = poll.poll(&mut events, timeout) {
157
+ return Ok(due_fibers);
158
+ }
153
159
  };
154
160
 
155
161
  for event in events.iter() {
@@ -19,6 +19,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
19
19
  scheduler.define_method("debug", method!(ItsiScheduler::debug, 1))?;
20
20
  scheduler.define_method("warn", method!(ItsiScheduler::warn, 1))?;
21
21
  scheduler.define_method("start_timer", method!(ItsiScheduler::start_timer, 2))?;
22
+ scheduler.define_method("clear_timer", method!(ItsiScheduler::clear_timer, 1))?;
22
23
  scheduler.define_method(
23
24
  "address_resolve",
24
25
  method!(ItsiScheduler::address_resolve, 1),
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-server"
3
- version = "0.1.20"
3
+ version = "0.2.2"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -57,12 +57,13 @@ fn init(ruby: &Ruby) -> Result<()> {
57
57
  request.define_method("rack_protocol", method!(ItsiHttpRequest::rack_protocol, 0))?;
58
58
  request.define_method("host", method!(ItsiHttpRequest::host, 0))?;
59
59
  request.define_method("headers", method!(ItsiHttpRequest::headers, 0))?;
60
+ request.define_method("uri", method!(ItsiHttpRequest::uri, 0))?;
60
61
  request.define_method("header", method!(ItsiHttpRequest::header, 1))?;
61
62
  request.define_method("[]", method!(ItsiHttpRequest::header, 1))?;
62
63
  request.define_method("scheme", method!(ItsiHttpRequest::scheme, 0))?;
63
64
  request.define_method("remote_addr", method!(ItsiHttpRequest::remote_addr, 0))?;
64
65
  request.define_method("port", method!(ItsiHttpRequest::port, 0))?;
65
- request.define_method("body", method!(ItsiHttpRequest::body, 0))?;
66
+ request.define_method("body_parts", method!(ItsiHttpRequest::body, 0))?;
66
67
  request.define_method("response", method!(ItsiHttpRequest::response, 0))?;
67
68
  request.define_method("json?", method!(ItsiHttpRequest::is_json, 0))?;
68
69
  request.define_method("html?", method!(ItsiHttpRequest::is_html, 0))?;
@@ -89,6 +89,7 @@ impl ItsiHttpRequest {
89
89
  self.content_type_str() == "application/json"
90
90
  }
91
91
 
92
+ #[allow(unexpected_cfgs)]
92
93
  pub fn url_params(&self) -> magnus::error::Result<RHash> {
93
94
  let captures = self
94
95
  .context
@@ -97,7 +98,19 @@ impl ItsiHttpRequest {
97
98
  .and_then(|re| re.captures(self.parts.uri.path()));
98
99
  if let Some(caps) = &captures {
99
100
  let re = self.context.matching_pattern.as_ref().unwrap();
100
- let params = RHash::with_capacity(caps.len());
101
+ let params = {
102
+ // when building against Ruby ≥ 3.2...
103
+ #[cfg(ruby_gte_3_2)]
104
+ {
105
+ RHash::with_capacity(caps.len())
106
+ }
107
+
108
+ // when building against Ruby < 3.2...
109
+ #[cfg(not(ruby_gte_3_2))]
110
+ {
111
+ RHash::new()
112
+ }
113
+ };
101
114
  for (i, group_name) in re.capture_names().enumerate().skip(1) {
102
115
  if let Some(name) = group_name {
103
116
  if let Some(m) = caps.get(i) {
@@ -312,6 +325,10 @@ impl ItsiHttpRequest {
312
325
  .collect::<Vec<(&str, &str)>>())
313
326
  }
314
327
 
328
+ pub(crate) fn uri(&self) -> MagnusResult<String> {
329
+ Ok(self.parts.uri.to_string())
330
+ }
331
+
315
332
  pub fn header(&self, name: String) -> MagnusResult<Option<Vec<&str>>> {
316
333
  let result: Vec<&str> = self
317
334
  .parts
@@ -2,8 +2,9 @@ use derive_more::Debug;
2
2
  use globset::{Glob, GlobSet, GlobSetBuilder};
3
3
  use magnus::error::Result;
4
4
  use nix::unistd::{close, fork, pipe, read};
5
- use notify::{event::ModifyKind, EventKind, RecommendedWatcher};
5
+ use notify::event::ModifyKind;
6
6
  use notify::{Event, RecursiveMode, Watcher};
7
+ use notify::{EventKind, RecommendedWatcher};
7
8
  use std::path::Path;
8
9
  use std::sync::mpsc::Sender;
9
10
  use std::time::{Duration, Instant};
@@ -15,6 +16,7 @@ use std::{
15
16
  sync::mpsc,
16
17
  thread::{self},
17
18
  };
19
+ use tracing::debug;
18
20
 
19
21
  /// Represents a set of patterns and commands.
20
22
  #[derive(Debug, Clone)]
@@ -105,7 +107,7 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
105
107
  let mut groups = Vec::new();
106
108
  for (pattern, commands) in pattern_groups.into_iter() {
107
109
  let base_dir = extract_and_canonicalize_base_dir(&pattern);
108
- let glob = Glob::new(&pattern).map_err(|e| {
110
+ let glob = Glob::new(pattern.trim_start_matches("./")).map_err(|e| {
109
111
  magnus::Error::new(
110
112
  magnus::exception::standard_error(),
111
113
  format!("Failed to create watch glob: {}", e),
@@ -143,6 +145,7 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
143
145
  notify::recommended_watcher(event_fn(sender)).expect("Failed to create watcher");
144
146
  for group in &groups {
145
147
  if watched_dirs.insert(group.base_dir.clone()) {
148
+ debug!("Watching {}{}", group.base_dir.display(), group.pattern);
146
149
  watcher
147
150
  .watch(&group.base_dir, RecursiveMode::Recursive)
148
151
  .expect("Failed to add watch");
@@ -153,7 +156,7 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
153
156
  for res in rx {
154
157
  match res {
155
158
  Ok(event) => {
156
- if !matches!(event.kind, EventKind::Modify(ModifyKind::Metadata(_))) {
159
+ if !matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) {
157
160
  continue;
158
161
  }
159
162
  let now = Instant::now();
@@ -163,6 +166,7 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
163
166
  if group.glob_set.is_match(rel_path)
164
167
  || rel_path.to_str().is_some_and(|s| s == group.pattern)
165
168
  {
169
+ debug!("Matched pattern: {:?}", group.pattern);
166
170
  // Check if we should debounce this event
167
171
  if let Some(last_triggered) = group.last_triggered {
168
172
  if now.duration_since(last_triggered) < DEBOUNCE_DURATION {
@@ -183,6 +187,10 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
183
187
  if command.len() > 1 {
184
188
  cmd.args(&command[1..]);
185
189
  }
190
+ debug!(
191
+ "Executing command: {:?} due to change in {:?}",
192
+ command, path
193
+ );
186
194
  match cmd.spawn() {
187
195
  Ok(mut child) => {
188
196
  if let Err(e) = child.wait() {