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
@@ -33,13 +33,14 @@ use reqwest::{
33
33
  Body, Client, Url,
34
34
  };
35
35
  use serde::Deserialize;
36
+ use tracing::{debug, info};
36
37
 
37
38
  #[derive(Debug, Clone, Deserialize)]
38
39
  pub struct Proxy {
39
40
  pub to: StringRewrite,
40
41
  pub backends: Vec<String>,
41
42
  pub backend_priority: BackendPriority,
42
- pub headers: HashMap<String, Option<ProxiedHeader>>,
43
+ pub headers: HashMap<String, Option<StringRewrite>>,
43
44
  pub verify_ssl: bool,
44
45
  pub timeout: u64,
45
46
  pub tls_sni: bool,
@@ -63,23 +64,6 @@ pub enum BackendPriority {
63
64
  Random,
64
65
  }
65
66
 
66
- #[derive(Debug, Clone, Deserialize)]
67
- pub enum ProxiedHeader {
68
- #[serde(rename(deserialize = "value"))]
69
- String(String),
70
- #[serde(rename(deserialize = "rewrite"))]
71
- StringRewrite(StringRewrite),
72
- }
73
-
74
- impl ProxiedHeader {
75
- pub fn to_string(&self, req: &HttpRequest, context: &HttpRequestContext) -> String {
76
- match self {
77
- ProxiedHeader::String(value) => value.clone(),
78
- ProxiedHeader::StringRewrite(rewrite) => rewrite.rewrite_request(req, context),
79
- }
80
- }
81
- }
82
-
83
67
  #[derive(Debug, Clone)]
84
68
  pub struct Resolver {
85
69
  backends: Arc<Vec<SocketAddr>>,
@@ -164,7 +148,7 @@ impl Proxy {
164
148
  for (name, header_opt) in self.headers.iter() {
165
149
  if let Some(header_value) = header_opt {
166
150
  // Compute the header value using the full HttpRequest.
167
- let value_str = header_value.to_string(req, context);
151
+ let value_str = header_value.rewrite_request(req, context);
168
152
  if let Ok(header_val) = http::HeaderValue::from_str(&value_str) {
169
153
  if let Ok(header_name) = name.parse::<http::header::HeaderName>() {
170
154
  headers.insert(header_name, header_val);
@@ -201,6 +185,7 @@ impl Proxy {
201
185
 
202
186
  // Add a Host header if not overridden.
203
187
  if !overriding_headers.contains_key("host") && !host_str.is_empty() {
188
+ debug!("Adding Host header: {}", host_str);
204
189
  builder = builder.header("Host", host_str);
205
190
  }
206
191
 
@@ -231,6 +216,7 @@ impl Proxy {
231
216
  Err(e) => {
232
217
  // Retry for connectivity-related errors.
233
218
  if e.is_connect() {
219
+ debug!(target: "middleware::proxy", "Connection error, retrying");
234
220
  last_err = Some(e);
235
221
  if attempt + 1 < max_attempts {
236
222
  continue;
@@ -278,6 +264,7 @@ impl MiddlewareLayer for Proxy {
278
264
  }
279
265
  })
280
266
  .collect::<Vec<_>>();
267
+ debug!(target: "middleware::proxy", "backends: {:?}", backends);
281
268
 
282
269
  self.client
283
270
  .set(
@@ -314,14 +301,20 @@ impl MiddlewareLayer for Proxy {
314
301
  context: &mut HttpRequestContext,
315
302
  ) -> Result<Either<HttpRequest, HttpResponse>> {
316
303
  let url = self.to.rewrite_request(&req, context);
304
+
317
305
  let accept: ResponseFormat = req.accept().into();
318
306
  let error_response = self.error_response.to_http_response(accept.clone()).await;
319
307
 
320
308
  let destination = match Url::parse(&url) {
321
309
  Ok(dest) => dest,
322
- Err(_) => return Ok(Either::Right(error_response)),
310
+ Err(_) => {
311
+ debug!(target: "middleware::proxy", "Failed to parse URL: {}", url);
312
+ return Ok(Either::Right(error_response));
313
+ }
323
314
  };
324
315
 
316
+ debug!(target: "middleware::proxy", "Proxying to: {:?}", destination);
317
+
325
318
  // Clone the headers before consuming the request.
326
319
  let req_headers = req.headers().clone();
327
320
  let host_str = destination.host_str().unwrap_or_else(|| {
@@ -331,6 +324,7 @@ impl MiddlewareLayer for Proxy {
331
324
  .unwrap_or("")
332
325
  });
333
326
 
327
+ info!("Extracted host str is {}", host_str);
334
328
  let req_info = RequestInfo {
335
329
  method: req.method().clone(),
336
330
  headers: req_headers.clone(),
@@ -373,6 +367,8 @@ impl MiddlewareLayer for Proxy {
373
367
 
374
368
  let response = match reqwest_response_result {
375
369
  Ok(response) => {
370
+ debug!(target: "middleware::proxy", "Response {} received", response.status());
371
+
376
372
  let status = response.status();
377
373
  let mut builder = Response::builder().status(status);
378
374
  for (hn, hv) in response.headers() {
@@ -387,6 +383,7 @@ impl MiddlewareLayer for Proxy {
387
383
  response.unwrap_or(error_response)
388
384
  }
389
385
  Err(e) => {
386
+ debug!(target: "middleware::proxy", "Error {:?} received", e);
390
387
  if let Some(inner) = e.source() {
391
388
  if inner.downcast_ref::<MaxBodySizeReached>().is_some() {
392
389
  let mut max_body_response = Response::new(BoxBody::new(Empty::new()));
@@ -8,8 +8,10 @@ use async_trait::async_trait;
8
8
  use either::Either;
9
9
  use magnus::error::Result;
10
10
  use serde::Deserialize;
11
+ use std::collections::HashMap;
11
12
  use std::sync::{Arc, OnceLock};
12
13
  use std::time::Duration;
14
+ use tracing::{debug, error, warn};
13
15
 
14
16
  #[derive(Debug, Clone, Deserialize)]
15
17
  pub struct RateLimit {
@@ -19,6 +21,7 @@ pub struct RateLimit {
19
21
  #[serde(skip_deserializing)]
20
22
  pub rate_limiter: OnceLock<Arc<dyn RateLimiter>>,
21
23
  pub store_config: RateLimiterConfig,
24
+ pub trusted_proxies: HashMap<String, TokenSource>,
22
25
  #[serde(default = "too_many_requests_error_response")]
23
26
  pub error_response: ErrorResponse,
24
27
  }
@@ -55,7 +58,12 @@ impl MiddlewareLayer for RateLimit {
55
58
  let key_value = match &self.key {
56
59
  RateLimitKey::SocketAddress => {
57
60
  // Use the socket address from the context
58
- &context.addr
61
+ if self.trusted_proxies.contains_key(&context.addr) {
62
+ let source = self.trusted_proxies.get(&context.addr).unwrap();
63
+ source.extract_token(&req).unwrap_or(&context.addr)
64
+ } else {
65
+ &context.addr
66
+ }
59
67
  }
60
68
  RateLimitKey::Parameter(token_source) => {
61
69
  match token_source {
@@ -68,7 +76,7 @@ impl MiddlewareLayer for RateLimit {
68
76
  }
69
77
  } else {
70
78
  // If no token is found, skip rate limiting
71
- tracing::warn!("No token found in header for rate limiting");
79
+ warn!("No token found in header for rate limiting");
72
80
  return Ok(Either::Left(req));
73
81
  }
74
82
  }
@@ -77,7 +85,7 @@ impl MiddlewareLayer for RateLimit {
77
85
  value
78
86
  } else {
79
87
  // If no token is found, skip rate limiting
80
- tracing::warn!("No token found in query for rate limiting");
88
+ warn!("No token found in query for rate limiting");
81
89
  return Ok(Either::Left(req));
82
90
  }
83
91
  }
@@ -88,6 +96,7 @@ impl MiddlewareLayer for RateLimit {
88
96
  // Create a rate limit key
89
97
  let rate_limit_key = create_rate_limit_key(key_value, req.uri().path());
90
98
 
99
+ debug!(target: "middleware::rate_limit", "Rate limit key: {}", rate_limit_key);
91
100
  // Get the rate limiter
92
101
  if let Some(limiter) = self.rate_limiter.get() {
93
102
  // Check if rate limit is exceeded
@@ -95,8 +104,12 @@ impl MiddlewareLayer for RateLimit {
95
104
  let limit = self.requests;
96
105
 
97
106
  match limiter.check_limit(&rate_limit_key, limit, timeout).await {
98
- Ok(_) => Ok(Either::Left(req)),
107
+ Ok(_) => {
108
+ debug!(target: "middleware::rate_limit", "Rate limit not exceeded");
109
+ Ok(Either::Left(req))
110
+ }
99
111
  Err(RateLimitError::RateLimitExceeded { limit, ttl, .. }) => {
112
+ debug!(target: "middleware::rate_limit", "Rate limit exceeded. Limit: {}, ttl: {}", limit, ttl);
100
113
  let mut response = self
101
114
  .error_response
102
115
  .to_http_response(req.accept().into())
@@ -116,14 +129,12 @@ impl MiddlewareLayer for RateLimit {
116
129
  Ok(Either::Right(response))
117
130
  }
118
131
  Err(e) => {
119
- // Other error, log and allow request (fail open)
120
- tracing::error!("Rate limiter error: {:?}", e);
132
+ error!("Rate limiter error: {:?}", e);
121
133
  Ok(Either::Left(req))
122
134
  }
123
135
  }
124
136
  } else {
125
- // If rate limiter is not initialized, allow request
126
- tracing::warn!("Rate limiter not initialized");
137
+ warn!("Rate limiter not initialized");
127
138
  Ok(Either::Left(req))
128
139
  }
129
140
  }
@@ -1,21 +1,19 @@
1
+ use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
1
2
  use crate::{
2
- server::http_message_types::{HttpRequest, HttpResponse},
3
+ server::{
4
+ http_message_types::{HttpRequest, HttpResponse},
5
+ redirect_type::RedirectType,
6
+ },
3
7
  services::itsi_http_service::HttpRequestContext,
4
8
  };
5
-
6
- use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
7
-
8
9
  use async_trait::async_trait;
9
10
  use either::Either;
10
- use http::{Response, StatusCode};
11
+ use http::Response;
11
12
  use http_body_util::{combinators::BoxBody, Empty};
12
13
  use magnus::error::Result;
13
14
  use serde::Deserialize;
15
+ use tracing::debug;
14
16
 
15
- /// A simple API key filter.
16
- /// The API key can be given inside the header or a query string
17
- /// Keys are validated against a list of allowed key values (Changing these requires a restart)
18
- ///
19
17
  #[derive(Debug, Clone, Deserialize)]
20
18
  pub struct Redirect {
21
19
  pub to: StringRewrite,
@@ -24,19 +22,6 @@ pub struct Redirect {
24
22
  pub redirect_type: RedirectType,
25
23
  }
26
24
 
27
- #[derive(Debug, Clone, Deserialize, Default)]
28
- pub enum RedirectType {
29
- #[serde(rename(deserialize = "permanent"))]
30
- #[default]
31
- Permanent,
32
- #[serde(rename(deserialize = "temporary"))]
33
- Temporary,
34
- #[serde(rename(deserialize = "found"))]
35
- Found,
36
- #[serde(rename(deserialize = "moved_permanently"))]
37
- MovedPermanently,
38
- }
39
-
40
25
  #[async_trait]
41
26
  impl MiddlewareLayer for Redirect {
42
27
  async fn before(
@@ -55,21 +40,15 @@ impl Redirect {
55
40
  context: &mut HttpRequestContext,
56
41
  ) -> Result<HttpResponse> {
57
42
  let mut response = Response::new(BoxBody::new(Empty::new()));
58
- *response.status_mut() = match self.redirect_type {
59
- RedirectType::Permanent => StatusCode::PERMANENT_REDIRECT,
60
- RedirectType::Temporary => StatusCode::TEMPORARY_REDIRECT,
61
- RedirectType::MovedPermanently => StatusCode::MOVED_PERMANENTLY,
62
- RedirectType::Found => StatusCode::FOUND,
63
- };
64
- response.headers_mut().append(
65
- "Location",
66
- self.to.rewrite_request(req, context).parse().map_err(|e| {
67
- magnus::Error::new(
68
- magnus::exception::standard_error(),
69
- format!("Invalid Rewrite String: {:?}: {}", self.to, e),
70
- )
71
- })?,
72
- );
43
+ *response.status_mut() = self.redirect_type.status_code();
44
+ let destination = self.to.rewrite_request(req, context).parse().map_err(|e| {
45
+ magnus::Error::new(
46
+ magnus::exception::standard_error(),
47
+ format!("Invalid Rewrite String: {:?}: {}", self.to, e),
48
+ )
49
+ })?;
50
+ debug!(target: "middleware::redirect", "Redirecting to {:?}", destination);
51
+ response.headers_mut().append("Location", destination);
73
52
  Ok(response)
74
53
  }
75
54
  }
@@ -5,7 +5,7 @@ use crate::{
5
5
  services::itsi_http_service::HttpRequestContext,
6
6
  };
7
7
 
8
- use super::{FromValue, MiddlewareLayer};
8
+ use super::{FromValue, MiddlewareLayer, StringRewrite};
9
9
  use async_trait::async_trait;
10
10
  use either::Either;
11
11
  use http::HeaderName;
@@ -14,30 +14,40 @@ use serde::Deserialize;
14
14
 
15
15
  #[derive(Debug, Clone, Deserialize)]
16
16
  pub struct RequestHeaders {
17
- pub additions: HashMap<String, Vec<String>>,
17
+ pub additions: HashMap<String, Vec<StringRewrite>>,
18
18
  pub removals: Vec<String>,
19
19
  }
20
-
21
20
  #[async_trait]
22
21
  impl MiddlewareLayer for RequestHeaders {
23
22
  async fn before(
24
23
  &self,
25
24
  mut req: HttpRequest,
26
- _: &mut HttpRequestContext,
25
+ context: &mut HttpRequestContext,
27
26
  ) -> Result<Either<HttpRequest, HttpResponse>> {
28
- let headers = req.headers_mut();
29
- for removal in &self.removals {
30
- headers.remove(removal);
31
- }
27
+ let mut headers_to_add = Vec::new();
28
+
32
29
  for (header_name, header_values) in &self.additions {
33
- for header_value in header_values {
34
- if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
35
- if let Ok(parsed_header_value) = header_value.parse() {
36
- headers.append(parsed_header_name, parsed_header_value);
30
+ if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
31
+ for header_value in header_values {
32
+ if let Ok(parsed_header_value) =
33
+ header_value.rewrite_request(&req, context).parse()
34
+ {
35
+ headers_to_add.push((parsed_header_name.clone(), parsed_header_value));
37
36
  }
38
37
  }
39
38
  }
40
39
  }
40
+
41
+ let headers = req.headers_mut();
42
+
43
+ for removal in &self.removals {
44
+ headers.remove(removal);
45
+ }
46
+
47
+ for (name, value) in headers_to_add {
48
+ headers.append(name, value);
49
+ }
50
+
41
51
  Ok(Either::Left(req))
42
52
  }
43
53
  }
@@ -1,6 +1,6 @@
1
1
  use std::collections::HashMap;
2
2
 
3
- use super::{FromValue, MiddlewareLayer};
3
+ use super::{FromValue, MiddlewareLayer, StringRewrite};
4
4
  use crate::{
5
5
  server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
6
6
  };
@@ -10,26 +10,41 @@ use serde::Deserialize;
10
10
 
11
11
  #[derive(Debug, Clone, Deserialize)]
12
12
  pub struct ResponseHeaders {
13
- pub additions: HashMap<String, Vec<String>>,
13
+ pub additions: HashMap<String, Vec<StringRewrite>>,
14
14
  pub removals: Vec<String>,
15
15
  }
16
16
 
17
17
  #[async_trait]
18
18
  impl MiddlewareLayer for ResponseHeaders {
19
- async fn after(&self, mut resp: HttpResponse, _: &mut HttpRequestContext) -> HttpResponse {
20
- let headers = resp.headers_mut();
21
- for removal in &self.removals {
22
- headers.remove(removal);
23
- }
19
+ async fn after(
20
+ &self,
21
+ mut resp: HttpResponse,
22
+ context: &mut HttpRequestContext,
23
+ ) -> HttpResponse {
24
+ let mut headers_to_add = Vec::new();
25
+
24
26
  for (header_name, header_values) in &self.additions {
25
- for header_value in header_values {
26
- if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
27
- if let Ok(parsed_header_value) = header_value.parse() {
28
- headers.append(parsed_header_name, parsed_header_value);
27
+ if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
28
+ for header_value in header_values {
29
+ if let Ok(parsed_header_value) =
30
+ header_value.rewrite_response(&resp, context).parse()
31
+ {
32
+ headers_to_add.push((parsed_header_name.clone(), parsed_header_value));
29
33
  }
30
34
  }
31
35
  }
32
36
  }
37
+
38
+ let headers = resp.headers_mut();
39
+
40
+ for removal in &self.removals {
41
+ headers.remove(removal);
42
+ }
43
+
44
+ for (name, value) in headers_to_add {
45
+ headers.append(name, value);
46
+ }
47
+
33
48
  resp
34
49
  }
35
50
  }
@@ -19,6 +19,7 @@ use magnus::error::Result;
19
19
  use regex::Regex;
20
20
  use serde::Deserialize;
21
21
  use std::{collections::HashMap, path::PathBuf, sync::OnceLock, time::Duration};
22
+ use tracing::debug;
22
23
 
23
24
  #[derive(Debug, Deserialize)]
24
25
  pub struct StaticAssets {
@@ -60,6 +61,8 @@ impl MiddlewareLayer for StaticAssets {
60
61
  .set(Regex::new(&self.base_path).map_err(ItsiError::new)?)
61
62
  .map_err(ItsiError::new)?;
62
63
 
64
+ debug!(target: "middleware::static_assets", "Base path regexp: {}", self.base_path);
65
+
63
66
  self.file_server
64
67
  .set(StaticFileServer::new(StaticFileServerConfig {
65
68
  root_dir: self.root_dir.clone(),
@@ -68,6 +71,7 @@ impl MiddlewareLayer for StaticAssets {
68
71
  max_entries: self.max_files_in_memory,
69
72
  try_html_extension: self.try_html_extension,
70
73
  max_file_size: self.max_file_size_in_memory,
74
+ headers: self.headers.clone(),
71
75
  recheck_interval: Duration::from_secs(self.file_check_interval),
72
76
  serve_hidden_files: self.serve_hidden_files,
73
77
  allowed_extensions: self.allowed_extensions.clone(),
@@ -83,11 +87,12 @@ impl MiddlewareLayer for StaticAssets {
83
87
  ) -> Result<Either<HttpRequest, HttpResponse>> {
84
88
  // Only handle GET and HEAD requests
85
89
  if req.method() != Method::GET && req.method() != Method::HEAD {
90
+ debug!(target: "middleware::static_assets", "Refusing to handle non-GET/HEAD request");
86
91
  return Ok(Either::Left(req));
87
92
  }
88
93
  let abs_path = req.uri().path();
89
94
  let rel_path = if !self.relative_path {
90
- abs_path
95
+ abs_path.trim_start_matches("/")
91
96
  } else {
92
97
  let base_path = self
93
98
  .base_path_regex
@@ -104,6 +109,7 @@ impl MiddlewareLayer for StaticAssets {
104
109
  }
105
110
  };
106
111
 
112
+ debug!(target: "middleware::static_assets", "Asset path is {}", rel_path);
107
113
  // Determine if this is a HEAD request
108
114
  let is_head_request = req.method() == Method::HEAD;
109
115
 
@@ -98,8 +98,13 @@ impl StringRewrite {
98
98
  }
99
99
  }
100
100
  other => {
101
- // Try using the context's matching regex if available.
102
- if let Some(caps) = &captures {
101
+ if let Some(header_val) = req.headers().get(other) {
102
+ if let Ok(s) = header_val.to_str() {
103
+ s.to_string()
104
+ } else {
105
+ "".to_string()
106
+ }
107
+ } else if let Some(caps) = &captures {
103
108
  if let Some(m) = caps.name(other) {
104
109
  m.as_str().to_string()
105
110
  } else {
@@ -146,8 +151,13 @@ impl StringRewrite {
146
151
  }
147
152
  }
148
153
  other => {
149
- if let Some(header_value) = resp.headers().get(other) {
150
- format!("{:?}", header_value)
154
+ // Try pulling from response headers first
155
+ if let Some(val) = resp.headers().get(other) {
156
+ if let Ok(s) = val.to_str() {
157
+ s.to_string()
158
+ } else {
159
+ "".to_string()
160
+ }
151
161
  } else {
152
162
  format!("{{{}}}", other)
153
163
  }
@@ -1,5 +1,7 @@
1
1
  use serde::{Deserialize, Serialize};
2
2
 
3
+ use crate::server::http_message_types::{HttpRequest, RequestExt};
4
+
3
5
  #[derive(Debug, Clone, Serialize, Deserialize)]
4
6
  pub enum TokenSource {
5
7
  #[serde(rename(deserialize = "header"))]
@@ -10,3 +12,20 @@ pub enum TokenSource {
10
12
  #[serde(rename(deserialize = "query"))]
11
13
  Query(String),
12
14
  }
15
+
16
+ impl TokenSource {
17
+ pub fn extract_token<'req>(&self, req: &'req HttpRequest) -> Option<&'req str> {
18
+ match self {
19
+ TokenSource::Header { name, prefix } => req.headers().get(name).and_then(|value| {
20
+ value.to_str().ok().and_then(|value| {
21
+ if let Some(prefix) = prefix {
22
+ value.strip_prefix(prefix).map(|v| v.trim())
23
+ } else {
24
+ Some(value)
25
+ }
26
+ })
27
+ }),
28
+ TokenSource::Query(query_name) => req.query_param(query_name),
29
+ }
30
+ }
31
+ }
@@ -8,8 +8,11 @@ use magnus::{
8
8
  pub use middleware::Middleware;
9
9
  pub use middlewares::*;
10
10
  use regex::{Regex, RegexSet};
11
- use std::{collections::HashMap, sync::Arc};
12
- use tracing::{debug, info};
11
+ use std::{
12
+ collections::{hash_map::Entry::Vacant, HashMap},
13
+ sync::Arc,
14
+ };
15
+ use tracing::debug;
13
16
 
14
17
  use super::http_message_types::HttpRequest;
15
18
 
@@ -132,6 +135,8 @@ impl MiddlewareStack {
132
135
  impl MiddlewareSet {
133
136
  pub fn new(routes_raw: Option<HeapVal>) -> Result<Self> {
134
137
  let mut unique_middlewares = HashMap::new();
138
+ clear_value_cache();
139
+
135
140
  if let Some(routes_raw) = routes_raw {
136
141
  let mut stacks = HashMap::new();
137
142
  let mut routes = vec![];
@@ -162,22 +167,54 @@ impl MiddlewareSet {
162
167
  format!("middleware must be a hash. Got {:?}", routes_raw),
163
168
  ))?;
164
169
 
170
+ let ruby = Ruby::get().unwrap();
165
171
  let mut layers = middleware
166
172
  .enumeratorize("each", ())
167
- .map(|pair| {
173
+ .flat_map(|pair| {
168
174
  let pair = RArray::from_value(pair.unwrap()).unwrap();
169
175
  let middleware_type: String = pair.entry(0).unwrap();
170
176
  let value: Value = pair.entry(1).unwrap();
171
- info!("Parsing middleware from value {}", value);
172
- let middleware = MiddlewareSet::parse_middleware(middleware_type, value);
173
- if let Ok(middleware) = middleware.as_ref() {
174
- unique_middlewares.insert(value.as_raw(), middleware.clone());
177
+ let middleware_values: Vec<Value> = if value.is_kind_of(ruby.class_array())
178
+ {
179
+ RArray::from_value(value)
180
+ .ok_or_else(|| {
181
+ magnus::Error::new(
182
+ magnus::exception::type_error(),
183
+ "Expected array",
184
+ )
185
+ })
186
+ .unwrap()
187
+ .into_iter()
188
+ .collect()
189
+ } else {
190
+ vec![value]
175
191
  };
176
- middleware
192
+ middleware_values
193
+ .into_iter()
194
+ .map(|value| {
195
+ let middleware =
196
+ if let Vacant(e) = unique_middlewares.entry(value.as_raw()) {
197
+ let middleware = MiddlewareSet::parse_middleware(
198
+ middleware_type.clone(),
199
+ value,
200
+ );
201
+ if let Ok(middleware) = middleware.as_ref() {
202
+ e.insert(middleware.clone());
203
+ };
204
+ middleware
205
+ } else {
206
+ Ok(unique_middlewares.get(&value.as_raw()).unwrap().clone())
207
+ };
208
+ middleware
209
+ })
210
+ .collect::<Vec<Result<Middleware>>>()
177
211
  })
178
212
  .collect::<Result<Vec<_>>>()?;
179
213
  routes.push(route_raw);
180
214
  layers.sort();
215
+
216
+ debug!("Middleware at index: {} has layers: {:?}", index, layers);
217
+
181
218
  stacks.insert(
182
219
  index,
183
220
  MiddlewareStack {
@@ -260,6 +297,7 @@ impl MiddlewareSet {
260
297
  pub fn parse_middleware(middleware_type: String, parameters: Value) -> Result<Middleware> {
261
298
  let mw_type = middleware_type.clone();
262
299
 
300
+ debug!(target: "itsi_server::middleware_stack", "Parsing middleware: {} from {}", mw_type, parameters);
263
301
  let result = (move || -> Result<Middleware> {
264
302
  match mw_type.as_str() {
265
303
  "allow_list" => Ok(Middleware::AllowList(AllowList::from_value(parameters)?)),
@@ -290,7 +328,7 @@ impl MiddlewareSet {
290
328
  "static_response" => Ok(Middleware::StaticResponse(StaticResponse::from_value(
291
329
  parameters,
292
330
  )?)),
293
- "compression" => Ok(Middleware::Compression(Compression::from_value(
331
+ "compress" => Ok(Middleware::Compression(Compression::from_value(
294
332
  parameters,
295
333
  )?)),
296
334
  "log_requests" => Ok(Middleware::LogRequests(LogRequests::from_value(
@@ -306,6 +344,8 @@ impl MiddlewareSet {
306
344
  }
307
345
  })();
308
346
 
347
+ debug!(target: "itsi_server::middleware_stack", "Stack layer init result {:?}", result);
348
+
309
349
  match result {
310
350
  Ok(result) => Ok(result),
311
351
  Err(err) => Err(magnus::Error::new(
@@ -319,10 +359,6 @@ impl MiddlewareSet {
319
359
  }
320
360
 
321
361
  pub async fn initialize_layers(&self) -> Result<()> {
322
- info!(
323
- "Unique middleware keys: {:?}",
324
- self.unique_middlewares.keys()
325
- );
326
362
  for middleware in self.unique_middlewares.values() {
327
363
  middleware.initialize().await?;
328
364
  }
@@ -5,6 +5,7 @@ pub mod io_stream;
5
5
  pub mod lifecycle_event;
6
6
  pub mod middleware_stack;
7
7
  pub mod process_worker;
8
+ pub mod redirect_type;
8
9
  pub mod request_job;
9
10
  pub mod serve_strategy;
10
11
  pub mod signal;
@@ -0,0 +1,26 @@
1
+ use http::StatusCode;
2
+ use serde::Deserialize;
3
+
4
+ #[derive(Debug, Clone, Deserialize, Default)]
5
+ pub enum RedirectType {
6
+ #[serde(rename(deserialize = "permanent"))]
7
+ #[default]
8
+ Permanent,
9
+ #[serde(rename(deserialize = "temporary"))]
10
+ Temporary,
11
+ #[serde(rename(deserialize = "found"))]
12
+ Found,
13
+ #[serde(rename(deserialize = "moved_permanently"))]
14
+ MovedPermanently,
15
+ }
16
+
17
+ impl RedirectType {
18
+ pub fn status_code(&self) -> StatusCode {
19
+ match self {
20
+ RedirectType::Permanent => StatusCode::PERMANENT_REDIRECT,
21
+ RedirectType::Temporary => StatusCode::TEMPORARY_REDIRECT,
22
+ RedirectType::Found => StatusCode::FOUND,
23
+ RedirectType::MovedPermanently => StatusCode::MOVED_PERMANENTLY,
24
+ }
25
+ }
26
+ }