itsi 0.1.20 → 0.2.3

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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -8
  3. data/Cargo.lock +29 -30
  4. data/LICENSE.txt +698 -0
  5. data/README.md +16 -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 +148 -66
  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 +2 -3
  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/directory_listing.jpg +0 -0
  87. data/docs/content/error_page.jpg +0 -0
  88. data/docs/content/faqs/_index.md +5 -3
  89. data/docs/content/features/_index.md +56 -50
  90. data/docs/content/getting_started/_index.md +8 -5
  91. data/docs/content/getting_started/local_development.md +76 -9
  92. data/docs/content/getting_started/logging.md +15 -9
  93. data/docs/content/getting_started/running_itsi_in_production.md +5 -3
  94. data/docs/content/getting_started/signals.md +37 -0
  95. data/docs/content/itsi_scheduler/_index.md +8 -7
  96. data/docs/content/utilities/_index.md +13 -0
  97. data/docs/content/utilities/config_file_testing.md +17 -0
  98. data/docs/content/utilities/passfile_generator.md +41 -0
  99. data/docs/content/utilities/route_testing.md +27 -0
  100. data/docs/content/utilities/secrets_management.md +30 -0
  101. data/docs/hugo.yaml +4 -1
  102. data/fairytale.txt +3 -4
  103. data/gems/scheduler/Cargo.lock +74 -17
  104. data/gems/scheduler/README.md +4 -5
  105. data/gems/scheduler/Rakefile +0 -4
  106. data/gems/scheduler/itsi-scheduler.gemspec +2 -2
  107. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  108. data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
  109. data/gems/scheduler/test/test_active_record.rb +12 -7
  110. data/gems/server/Cargo.lock +28 -29
  111. data/gems/server/Rakefile +0 -4
  112. data/gems/server/exe/itsi +13 -2
  113. data/gems/server/itsi-server.gemspec +2 -2
  114. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
  115. data/gems/server/lib/itsi/http_request.rb +58 -30
  116. data/gems/server/lib/itsi/http_response.rb +10 -7
  117. data/gems/server/lib/itsi/passfile.rb +6 -7
  118. data/gems/server/lib/itsi/server/config/config_helpers.rb +41 -29
  119. data/gems/server/lib/itsi/server/config/dsl.rb +22 -442
  120. data/gems/server/lib/itsi/server/config/known_paths.rb +14 -7
  121. data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
  122. data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
  123. data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
  124. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
  125. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
  126. data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
  127. data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
  128. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
  129. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
  130. data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
  131. data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
  132. data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
  133. data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
  134. data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
  135. data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
  136. data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
  137. data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
  138. data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
  139. data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
  140. data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
  141. data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
  142. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
  143. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
  144. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
  145. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
  146. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
  147. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
  148. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
  149. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
  150. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
  151. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
  152. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
  153. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
  154. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
  155. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
  156. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
  157. data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
  158. data/gems/server/lib/itsi/server/config/middleware/error_response.md +74 -0
  159. data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
  160. data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
  161. data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
  162. data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
  163. data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
  164. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
  165. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
  166. data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
  167. data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
  168. data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
  169. data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
  170. data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
  171. data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
  172. data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
  173. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
  174. data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
  175. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
  176. data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
  177. data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
  178. data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
  179. data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
  180. data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
  181. data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
  182. data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
  183. data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
  184. data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
  185. data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
  186. data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
  187. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +113 -0
  188. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
  189. data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
  190. data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
  191. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
  192. data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
  193. data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
  194. data/gems/server/lib/itsi/server/config/option.rb +4 -0
  195. data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
  196. data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
  197. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
  198. data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
  199. data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
  200. data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
  201. data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
  202. data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
  203. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
  204. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
  205. data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
  206. data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
  207. data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
  208. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
  209. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
  210. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
  211. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
  212. data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
  213. data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
  214. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
  215. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
  216. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
  217. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
  218. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
  219. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
  220. data/gems/server/lib/itsi/server/config/options/include.md +20 -0
  221. data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
  222. data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
  223. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
  224. data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
  225. data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
  226. data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
  227. data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
  228. data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
  229. data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
  230. data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
  231. data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
  232. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
  233. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
  234. data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
  235. data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
  236. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
  237. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
  238. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
  239. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
  240. data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
  241. data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
  242. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
  243. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
  244. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
  245. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
  246. data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
  247. data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
  248. data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
  249. data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
  250. data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
  251. data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
  252. data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
  253. data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
  254. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
  255. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
  256. data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
  257. data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
  258. data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
  259. data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
  260. data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
  261. data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
  262. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
  263. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
  264. data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
  265. data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
  266. data/gems/server/lib/itsi/server/config/typed_struct.rb +68 -32
  267. data/gems/server/lib/itsi/server/config.rb +163 -119
  268. data/gems/server/lib/itsi/server/default_app/default_app.rb +1 -1
  269. data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
  270. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +4 -5
  271. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +10 -4
  272. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +2 -3
  273. data/gems/server/lib/itsi/server/rack_interface.rb +0 -1
  274. data/gems/server/lib/itsi/server/route_tester.rb +61 -9
  275. data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
  276. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
  277. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +13 -10
  278. data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
  279. data/gems/server/lib/itsi/server/version.rb +1 -1
  280. data/gems/server/lib/itsi/server.rb +111 -27
  281. data/gems/server/lib/itsi/standard_headers.rb +80 -80
  282. data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
  283. data/gems/server/test/helpers/test_helper.rb +90 -29
  284. data/gems/server/test/middleware/allow_list.rb +128 -0
  285. data/gems/server/test/middleware/auth_api_key.rb +141 -0
  286. data/gems/server/test/middleware/auth_basic.rb +91 -0
  287. data/gems/server/test/middleware/auth_jwt.rb +214 -0
  288. data/gems/server/test/middleware/cache_control.rb +82 -0
  289. data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
  290. data/gems/server/test/middleware/compression.rb +89 -0
  291. data/gems/server/test/middleware/cors.rb +113 -0
  292. data/gems/server/test/middleware/csp.rb +62 -0
  293. data/gems/server/test/middleware/deny_list.rb +131 -0
  294. data/gems/server/test/middleware/endpoint.rb +300 -0
  295. data/gems/server/test/middleware/etag.rb +75 -0
  296. data/gems/server/test/middleware/grpc/grpc.rb +158 -0
  297. data/gems/server/test/middleware/grpc/test_service.proto +32 -0
  298. data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
  299. data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
  300. data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
  301. data/gems/server/test/middleware/header_interpolation.rb +35 -0
  302. data/gems/server/test/middleware/intrusion_protection.rb +259 -0
  303. data/gems/server/test/middleware/location.rb +220 -0
  304. data/gems/server/test/middleware/max_body.rb +20 -0
  305. data/gems/server/test/middleware/proxy.rb +415 -0
  306. data/gems/server/test/middleware/rate_limit.rb +211 -0
  307. data/gems/server/test/middleware/redirect.rb +85 -0
  308. data/gems/server/test/middleware/request_headers.rb +50 -0
  309. data/gems/server/test/middleware/response_headers.rb +50 -0
  310. data/gems/server/test/middleware/static_assets.rb +374 -0
  311. data/gems/server/test/middleware/static_response.rb +56 -0
  312. data/gems/server/test/middleware/string_rewrite.rb +112 -0
  313. data/gems/server/test/middleware/test_log_requests.rb +54 -2
  314. data/gems/server/test/options/bind.rb +47 -0
  315. data/gems/server/test/options/header_read_timeout.rb +23 -0
  316. data/gems/server/test/options/test_request_timeout.rb +16 -0
  317. data/gems/server/test/options/test_workers.rb +11 -6
  318. data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
  319. data/lib/itsi/version.rb +1 -1
  320. data/tasks.txt +15 -72
  321. metadata +209 -10
  322. data/examples/static_assets_example.rb +0 -83
  323. data/gems/server/lib/itsi/server/default_config/Itsi-rackup.rb +0 -119
@@ -2,12 +2,12 @@ module Itsi
2
2
  class Server
3
3
  module Config
4
4
  module ConfigHelpers
5
-
6
5
  def self.load_and_register(klass)
7
- config_type = klass.name.split("::").last.downcase
6
+ config_type = klass.name.split("::").last.downcase.gsub(/([a-z]()[A-Z])/, '\1_\2')
7
+
8
8
  listing = [
9
- Dir[File.expand_path(File.dirname(__FILE__) + "/#{config_type}/**.rb")],
10
- Dir[File.expand_path(File.dirname(__FILE__) + "/#{config_type}s/**.rb")]
9
+ Dir[File.expand_path(File.dirname(__FILE__) + "/#{config_type}/**/*.rb")],
10
+ Dir[File.expand_path(File.dirname(__FILE__) + "/#{config_type}s/**/*.rb")]
11
11
  ].flatten
12
12
 
13
13
  listing.each do |file|
@@ -16,32 +16,35 @@ module Itsi
16
16
  following = klass.subclasses
17
17
  new_class = (following - current).first
18
18
 
19
- documentation_file = "#{file[/(.*)\.rb/,1]}.md"
20
- if File.exist?(documentation_file) && new_class
21
- new_class.documentation IO.read(documentation_file)
22
- .gsub(/^---.*?\n.*?-+/m,'') # Strip frontmatter
23
- .gsub(/^(```.*?)\{.*?\}.*$/, "\\1") # Strip filename from code blocks
24
- .gsub(/^\{\{[^\}]+\}\}/, "") # Strip Hugo blocks
25
- end
19
+ documentation_file = "#{file[/(.*)\.rb/, 1]}.md"
20
+ documentation_file = "#{file[%r{(.*)/[^/]+\.rb}, 1]}/_index.md" unless File.exist?(documentation_file)
21
+ next unless File.exist?(documentation_file) && new_class
22
+
23
+ new_class.documentation IO.read(documentation_file)
24
+ .gsub(/^---.*?\n.*?-+/m, "") # Strip frontmatter
25
+ .gsub(/^(```.*?)\{.*?\}.*$/, "\\1") # Strip filename from code blocks
26
+ .gsub(/^\{\{[^}]+\}\}/, "") # Strip Hugo blocks
26
27
  end
27
28
  end
28
29
 
29
- def normalize_keys!(hash, expected=[])
30
+ def normalize_keys!(hash, expected = [])
30
31
  hash.keys.each do |key|
31
32
  value = hash.delete(key)
32
33
  key = key.to_s.downcase.to_sym
33
34
  hash[key] = value
34
35
  raise "Unexpected key: #{key}" unless expected.include?(key)
36
+
35
37
  expected -= [key]
36
38
  end
37
- raise "Missing required keys: #{expected.join(', ')}" unless expected.empty?
39
+ raise "Missing required keys: #{expected.join(", ")}" unless expected.empty?
40
+
38
41
  hash
39
42
  end
40
43
 
41
- def self.included(cls)
42
- def cls.inherited(base)
44
+ def self.included(cls) # rubocop:disable Metrics/PerceivedComplexity,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
45
+ def cls.inherited(base) # rubocop:disable Metrics/MethodLength,Lint/MissingSuper,Metrics/PerceivedComplexity
43
46
  %i[detail documentation insert_text schema].each do |attr|
44
- base.define_singleton_method(attr) do |value=nil|
47
+ base.define_singleton_method(attr) do |value = nil|
45
48
  @middleware_class_attrs ||= {}
46
49
  if value
47
50
  @middleware_class_attrs[attr] = value
@@ -50,15 +53,17 @@ module Itsi
50
53
  end
51
54
  end
52
55
 
53
- base.define_method(attr) do |value=nil|
56
+ base.define_method(attr) do |_value = nil|
54
57
  self.class.send(attr)
55
58
  end
56
59
  end
57
60
 
58
- def base.schema(value=nil, &blk)
61
+ def base.schema(value = nil, &blk)
59
62
  @middleware_class_attrs ||= {}
60
63
  if blk
61
64
  @middleware_class_attrs[:schema] = TypedStruct.new(&blk)
65
+ elsif value
66
+ @middleware_class_attrs[:schema] = value
62
67
  else
63
68
  @middleware_class_attrs[:schema]
64
69
  end
@@ -69,23 +74,30 @@ module Itsi
69
74
 
70
75
  config_type = cls.name.split("::").last.downcase
71
76
 
72
- cls.define_singleton_method("#{config_type}_name") do |name=self.name|
77
+ cls.define_singleton_method("#{config_type}_name") do |name = self.name|
73
78
  @config_name ||= name.split("::").last.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym
74
79
  end
80
+ cls.define_method(:opt_name) { self.class.send("#{config_type}_name") }
81
+ cls.define_method(:location) { @location }
75
82
  end
76
83
 
77
- def initialize(location, params)
78
- @location = location
79
- @params = case self.schema
80
- when TypedStruct::Validation
81
- self.schema.validate!(params)
82
- else
83
- self.schema.new(params).to_h
84
+ def initialize(location, params = {})
85
+ if !self.class.ancestors.include?(Middleware) && !location.parent.nil?
86
+ raise "#{opt_name} must be set at the top level"
84
87
  end
85
- end
86
88
 
87
- def build!
88
- @params
89
+ @location = location
90
+ @params = case schema
91
+ when TypedStruct::Validation
92
+ schema.validate!(params)
93
+ when Array
94
+ default, validation = schema
95
+ params ? validation.validate!(params) : default
96
+ when nil
97
+ nil
98
+ else
99
+ schema.new(params).to_h
100
+ end
89
101
  end
90
102
  end
91
103
  end
@@ -6,10 +6,10 @@ module Itsi
6
6
  require_relative "option"
7
7
  require_relative "middleware"
8
8
 
9
- attr_reader :parent, :children, :middleware, :controller_class, :routes, :http_methods, :protocols,
9
+ attr_reader :parent, :children, :middleware, :controller, :routes, :http_methods, :protocols,
10
10
  :hosts, :ports, :extensions, :content_types, :accepts, :options
11
11
 
12
- def self.evaluate(config = Itsi::Server::Config.config_file_path, &blk)
12
+ def self.evaluate(config = Itsi::Server::Config.config_file_path, &blk) # rubocop:disable Metrics/MethodLength
13
13
  config = new(routes: ["/"]) do
14
14
  if blk
15
15
  instance_exec(&blk)
@@ -17,13 +17,14 @@ module Itsi
17
17
  code = IO.read(config)
18
18
  instance_eval(code, config.to_s, 1)
19
19
  end
20
+ location("*") {}
20
21
  end
21
22
  [config.options, config.errors]
22
- rescue Exception => e
23
+ rescue Exception => e # rubocop:disable Lint/RescueException
23
24
  [{}, [[e, e.backtrace[0]]]]
24
25
  end
25
26
 
26
- def initialize(
27
+ def initialize( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
27
28
  parent = nil,
28
29
  routes: [],
29
30
  methods: [],
@@ -39,7 +40,6 @@ module Itsi
39
40
  @parent = parent
40
41
  @children = []
41
42
  @middleware = {}
42
- @controller_class = nil
43
43
 
44
44
  @controller = controller
45
45
  @routes = Array(routes).flatten
@@ -57,7 +57,7 @@ module Itsi
57
57
  @options[:middleware_loaders].each(&:call)
58
58
  @middleware[:app] ||= {}
59
59
  @middleware[:app][:app_proc] = @middleware[:app]&.[](:preloader)&.call || DEFAULT_APP[]
60
- flatten_routes
60
+ [flatten_routes, Config.errors_to_error_lines(errors)]
61
61
  end
62
62
  }
63
63
 
@@ -66,14 +66,14 @@ module Itsi
66
66
  end
67
67
 
68
68
  def errors
69
- @children.map(&:errors).flatten + @errors
69
+ @children.map(&:errors).flatten(1) + @errors
70
70
  end
71
71
 
72
72
  Option.subclasses.each do |option|
73
73
  option_name = option.option_name
74
74
  define_method(option_name) do |*args, **kwargs, &blk|
75
- @options[option_name] = option.new(self, *args, **kwargs, &blk).build!
76
- rescue => e
75
+ option.new(self, *args, **kwargs, &blk).build!
76
+ rescue Exception => e # rubocop:disable Lint/RescueException
77
77
  @errors << [e, caller[1]]
78
78
  end
79
79
  end
@@ -81,452 +81,28 @@ module Itsi
81
81
  Middleware.subclasses.each do |middleware|
82
82
  middleware_name = middleware.middleware_name
83
83
  define_method(middleware_name) do |*args, **kwargs, &blk|
84
- @middleware[middleware_name] = middleware.new(@parent, *args, **kwargs, &blk).build!
85
- rescue => e
84
+ middleware.new(self, *args, **kwargs, &blk).build!
85
+ rescue Config::Endpoint::InvalidHandlerException => e
86
+ @errors << [e, "#{e.backtrace[0]}:in #{e.message}"]
87
+ rescue Exception => e # rubocop:disable Lint/RescueException
86
88
  @errors << [e, caller[1]]
87
89
  end
88
90
  end
89
91
 
90
- def oob_gc_responses_threshold(threshold)
91
- raise "OOB GC responses threshold must be set at the root" unless @parent.nil?
92
-
93
- @options[:oob_gc_responses_threshold] = threshold.to_i
94
- end
95
-
96
- def log_level(level)
97
- raise "Log level must be set at the root" unless @parent.nil?
98
-
99
- @options[:log_level] = level.to_s
100
- end
101
-
102
- def log_target(target)
103
- raise "Log target must be set at the root" unless @parent.nil?
104
-
105
- @options[:log_target] = target.to_s
106
- end
107
-
108
- def log_target_filters(target_filters)
109
- raise "Log target filters must be set at the root" unless @parent.nil?
110
-
111
- @options[:log_target_filters] = target_filters
112
- end
113
-
114
- def log_format(target)
115
- raise "Log format must be set at the root" unless @parent.nil?
116
-
117
- @options[:log_format] = target.to_s
118
- end
119
-
120
- def get(route, app_proc = nil, nonblocking: false, &blk)
121
- endpoint(route, [:get], app_proc, nonblocking: nonblocking, &blk)
122
- end
123
-
124
- def post(route, app_proc = nil, nonblocking: false, &blk)
125
- endpoint(route, [:post], app_proc, nonblocking: nonblocking, &blk)
126
- end
127
-
128
- def put(route, app_proc = nil, nonblocking: false, &blk)
129
- endpoint(route, [:put], app_proc, nonblocking: nonblocking, &blk)
130
- end
131
-
132
- def delete(route, app_proc = nil, nonblocking: false, &blk)
133
- endpoint(route, [:delete], app_proc, nonblocking: nonblocking, &blk)
134
- end
135
-
136
- def patch(route, app_proc = nil, nonblocking: false, &blk)
137
- endpoint(route, [:patch], app_proc, nonblocking: nonblocking, &blk)
138
- end
139
-
140
- def endpoint(route=nil, methods=[], app_proc = nil, nonblocking: false, &blk)
141
- raise "You must provide either a block or an explicit handler for the endpoint" if app_proc.nil? && blk.nil?
142
-
143
- app_proc = @controller.method(app_proc).to_proc if app_proc.is_a?(Symbol)
144
-
145
- app_proc ||= blk
146
- num_required, keywords = Itsi::Server::TypedHandlers::SourceParser.extract_expr_from_source_location(app_proc)
147
- params_schema = keywords[:params]
148
-
149
- if params_schema && num_required > 1
150
- raise "Cannot accept multiple required parameters in a single endpoint. A single typed or untyped params argument is supported"
151
- end
152
- if num_required > 2
153
- raise "Cannot accept more than two required parameters in a single endpoint. An can either accept a single request argument, or a request and a params argument (which may be typed or untyped)"
154
- end
155
- if num_required == 0
156
- raise "Cannot accept zero required parameters in a single endpoint. Endpoint must accept a request parameter"
157
- end
158
-
159
- accepts_params = !params_schema.nil? || num_required > 1
160
-
161
- if accepts_params
162
- app_proc = Itsi::Server::TypedHandlers.handler_for(app_proc, params_schema)
163
- end
164
-
165
- if route || http_methods.any?
166
- # For endpoints, it's usually assumed trailing slash and non-trailing slash behaviour is the same
167
- routes = route == "/" ? ["", "/"] : [route]
168
- location(*routes, methods: http_methods) do
169
- @middleware[:app] = { preloader: -> { app_proc }, nonblocking: nonblocking }
170
- end
171
- else
172
- app = { preloader: -> { app_proc }, nonblocking: nonblocking }
173
- @middleware[:app] = app
174
- location("*") do
175
- @middleware[:app] = app
176
- end
177
- end
178
- end
179
-
180
- def grpc(*handlers, reflection: true, nonblocking: false, **, &blk)
181
- if @middleware[:app] && @middleware[:app][:request_type].to_s != "grpc"
182
- raise "App has already been set. You can use only one of `run` and `rackup_file` or `grpc` per location"
183
- end
184
-
185
- grpc_reflection(handlers) if reflection
186
-
187
- handlers.each do |handler|
188
- location(Regexp.new("#{Regexp.escape(handler.class.service_name)}/(?:#{handler.class.rpc_descs.keys.map(&:to_s).join("|")})")) do
189
- @middleware[:app] = { preloader: -> { Itsi::Server::GrpcInterface.for(handler) }, request_type: "grpc", nonblocking: nonblocking }
190
- instance_exec(&blk)
191
- end
192
- end
193
- end
194
-
195
92
  def grpc_reflection(handlers)
196
93
  @grpc_reflected_services ||= []
197
94
  @grpc_reflected_services.concat(handlers)
198
95
 
199
- location(["grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo",
200
- "grpc.reflection.v1.ServerReflection/ServerReflectionInfo"]) do
96
+ location("grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo",
97
+ "grpc.reflection.v1.ServerReflection/ServerReflectionInfo") do
201
98
  @middleware[:app] = { preloader: lambda {
202
99
  Itsi::Server::GrpcInterface.reflection_for(handlers)
203
100
  }, request_type: "grpc" }
204
101
  end
205
102
  end
206
103
 
207
- def run(app, sendfile: true, nonblocking: false, path_info: "/")
208
- app_args = { preloader: -> { Itsi::Server::RackInterface.for(app) }, sendfile: sendfile, base_path: "^(?<base_path>#{paths_from_parent.gsub(/\.\*\)$/, ')')}).*$", path_info: path_info, nonblocking: nonblocking }
209
- base_path = "^(?<base_path>#{paths_from_parent.gsub(/\.\*\)$/, ')')}).*$"
210
- @middleware[:app] = app_args
211
- location("*") do
212
- @middleware[:app] = app_args
213
- end
214
- end
215
-
216
- def rackup_file(rackup_file, nonblocking: false, sendfile: true, path_info: "/")
217
- raise "Rackup file #{rackup_file} doesn't exist" unless File.exist?(rackup_file)
218
- app_args = { preloader: -> { Itsi::Server::RackInterface.for(rackup_file) }, sendfile: sendfile, base_path: "^(?<base_path>#{paths_from_parent.gsub(/\.\*\)$/, ')')}).*$", path_info: path_info, nonblocking: nonblocking }
219
- @middleware[:app] = app_args
220
- location("*") do
221
- @middleware[:app] = app_args
222
- end
223
- end
224
-
225
- def include(path)
226
- code = IO.read("#{path}.rb")
227
- instance_eval(code, "#{path}.rb", 1)
228
- end
229
-
230
- def reuse_address(reuse_address)
231
- raise "reuse_address must be set at the root" unless @parent.nil?
232
- @options[:reuse_address] = reuse_address
233
- end
234
-
235
- def reuse_port(reuse_port)
236
- raise "reuse_port must be set at the root" unless @parent.nil?
237
- @options[:reuse_port] = reuse_port
238
- end
239
-
240
- def listen_backlog(listen_backlog)
241
- raise "listen_backlog must be set at the root" unless @parent.nil?
242
- @options[:listen_backlog] = listen_backlog
243
- end
244
-
245
- def nodelay(nodelay)
246
- raise "nodelay must be set at the root" unless @parent.nil?
247
- @options[:nodelay] = nodelay
248
- end
249
-
250
- def recv_buffer_size(recv_buffer_size)
251
- raise "recv_buffer_size must be set at the root" unless @parent.nil?
252
- @options[:recv_buffer_size] = recv_buffer_size
253
- end
254
-
255
- def bind(bind_str)
256
- raise "Bind must be set at the root" unless @parent.nil?
257
-
258
- @options[:binds] ||= []
259
- @options[:binds] << bind_str.to_s
260
- end
261
-
262
- def after_fork(&block)
263
- raise "After fork must be set at the root" unless @parent.nil?
264
-
265
- @options[:hooks] ||= {}
266
- @options[:hooks][:after_fork] = block
267
- end
268
-
269
- def before_fork(&block)
270
- raise "Before fork must be set at the root" unless @parent.nil?
271
-
272
- @options[:hooks] ||= {}
273
- @options[:hooks][:before_fork] = block
274
- end
275
-
276
- def after_memory_threshold_reached(&block)
277
- raise "Before fork must be set at the root" unless @parent.nil?
278
-
279
- @options[:hooks] ||= {}
280
- @options[:hooks][:after_memory_threshold_reached] = block
281
- end
282
-
283
- def worker_memory_limit(memory_limit)
284
- raise "Worker memory limit must be set at the root" unless @parent.nil?
285
-
286
- @options[:worker_memory_limit] = memory_limit
287
- end
288
-
289
- def multithreaded_reactor(multithreaded)
290
- raise "Multithreaded reactor must be set at the root" unless @parent.nil?
291
-
292
- @options[:multithreaded_reactor] = !!multithreaded
293
- end
294
-
295
- def pin_worker_cores(pin_worker_cores)
296
- raise "Pin worker cores must be set at the root" unless @parent.nil?
297
-
298
- @options[:pin_worker_cores] = !!pin_worker_cores
299
- end
300
-
301
- def auto_reload_config!
302
- if ENV["BUNDLE_BIN_PATH"]
303
- watch "Itsi.rb", [%w[bundle exec itsi restart]]
304
- else
305
- watch "Itsi.rb", [%w[itsi restart]]
306
- end
307
- end
308
-
309
- def watch(path, commands)
310
- raise "Watch be set at the root" unless @parent.nil?
311
-
312
- @options[:notify_watchers] ||= []
313
- @options[:notify_watchers] << [path, commands]
314
- end
315
-
316
- def fiber_scheduler(klass_name = true)
317
- raise "Fiber scheduler must be set at the root" unless @parent.nil?
318
-
319
- klass_name = "Itsi::Scheduler" if klass_name == true
320
- @options[:scheduler_class] = klass_name if klass_name
321
- end
322
-
323
- def scheduler_threads(threads = 1)
324
- raise "Scheduler threads must be set at the root" unless @parent.nil?
325
-
326
- @options[:scheduler_threads] = threads
327
- end
328
-
329
- def request_timeout(request_timeout)
330
- raise "Request timeout must be set at the root" unless @parent.nil?
331
-
332
- @options[:request_timeout] = request_timeout
333
- end
334
-
335
- def preload(preload)
336
- raise "Preload must be set at the root" unless @parent.nil?
337
-
338
- @options[:preload] = preload
339
- end
340
-
341
- def shutdown_timeout(shutdown_timeout)
342
- raise "Shutdown timeout must be set at the root" unless @parent.nil?
343
-
344
- @options[:shutdown_timeout] = shutdown_timeout.to_f
345
- end
346
-
347
- def header_read_timeout(header_read_timeout)
348
- raise "Header read timeout must be set at the root" unless @parent.nil?
349
-
350
- @options[:header_read_timeout] = header_read_timeout.to_f
351
- end
352
-
353
- def stream_body(stream_body)
354
- raise "Stream body must be set at the root" unless @parent.nil?
355
-
356
- @options[:stream_body] = !!stream_body
357
- end
358
-
359
- def location(*routes, methods: [], protocols: [], hosts: [], ports: [], extensions: [], content_types: [],
360
- accepts: [], &block)
361
- build_child = lambda {
362
- @children << DSL.new(
363
- self,
364
- routes: routes,
365
- methods: Array(methods) | self.http_methods,
366
- protocols: Array(protocols) | self.protocols,
367
- hosts: Array(hosts) | self.hosts,
368
- ports: Array(ports) | self.ports,
369
- extensions: Array(extensions) | self.extensions,
370
- content_types: Array(content_types) | self.content_types,
371
- accepts: Array(accepts) | self.accepts,
372
- controller: @controller,
373
- &block
374
- )
375
- }
376
- if @parent.nil?
377
- @options[:middleware_loaders] << build_child
378
- else
379
- build_child[]
380
- end
381
- end
382
-
383
-
384
- def allow_list(**args)
385
- args[:allowed_patterns] = Array(args[:allowed_patterns]).map do |pattern|
386
- if pattern.is_a?(Regexp)
387
- pattern.source
388
- else
389
- pattern
390
- end
391
- end
392
- @middleware[:allow_list] = args
393
- end
394
-
395
- def deny_list(**args)
396
- args[:denied_patterns] = Array(args[:denied_patterns]).map do |pattern|
397
- if pattern.is_a?(Regexp)
398
- pattern.source
399
- else
400
- pattern
401
- end
402
- end
403
- @middleware[:deny_list] = args
404
- end
405
-
406
- def controller(controller)
407
- @controller = controller
408
- end
409
-
410
- def auth_basic(**args)
411
- if File.exist?(".itsi-credentials") && !args[:credential_file]
412
- args[:credential_file] = ".itsi-credentials"
413
- end
414
-
415
- if args[:credential_file] && File.exist?(args[:credential_file])
416
- args[:credential_pairs] = Passfile.load(args[:credential_file])
417
- end
418
-
419
- @middleware[:auth_basic] = args
420
- end
421
-
422
- def redirect(**args)
423
- @middleware[:redirect] = args
424
- end
425
-
426
- def proxy(**args)
427
- @middleware[:proxy] = args
428
- end
429
-
430
- def static_response(**args)
431
- args[:body] = args[:body].bytes
432
- @middleware[:static_response] = args
433
- end
434
-
435
- def auth_jwt(**args)
436
- @middleware[:auth_jwt] = args
437
- end
438
-
439
- def auth_api_key(**args)
440
- if args[:valid_keys] && args[:valid_keys].is_a?(Array)
441
- args[:valid_keys] = args[:valid_keys].each_with_index.map { |key, index| [index, key] }.to_h
442
- args[:key_id_source] = nil
443
- end
444
-
445
- if File.exist?(".itsi-credentials") && !args[:credential_file]
446
- args[:credential_file] = ".itsi-credentials"
447
- end
448
-
449
- if args[:credential_file] && File.exist?(args[:credential_file])
450
- args[:valid_keys] = Passfile.load(args[:credential_file])
451
- end
452
-
453
- @middleware[:auth_api_key] = args
454
- end
455
-
456
- def compress(**args)
457
- @middleware[:compression] = args
458
- end
459
-
460
- def request_headers(**args)
461
- @middleware[:request_headers] = args
462
- end
463
-
464
- def max_body(**args)
465
- @middleware[:max_body] = args
466
- end
467
-
468
- def response_headers(**args)
469
- @middleware[:response_headers] = args
470
- end
471
-
472
- def rate_limit(**args)
473
- @middleware[:rate_limit] = args
474
- end
475
-
476
- def cache_control(**args)
477
- @middleware[:cache_control] = args
478
- end
479
-
480
- def etag(**args)
481
- @middleware[:etag] = args
482
- end
483
-
484
- def csp(**args)
485
- @middleware[:csp] = args
486
- end
487
-
488
- def intrusion_protection(**args)
489
- args[:banned_url_patterns] = Array(args[:banned_url_patterns]).flatten.map do |pattern|
490
- if pattern.is_a?(Regexp)
491
- pattern.source
492
- else
493
- pattern
494
- end
495
- end
496
- @middleware[:intrusion_protection] = args
497
- end
498
-
499
- def cors(**args)
500
- @middleware[:cors] = args
501
- end
502
-
503
- def static_assets(**args)
504
- root_dir = args[:root_dir] || "."
505
-
506
- if !File.exist?(root_dir)
507
- warn "Warning: static_assets root_dir '#{root_dir}' does not exist!"
508
- elsif !File.directory?(root_dir)
509
- warn "Warning: static_assets root_dir '#{root_dir}' is not a directory!"
510
- end
511
-
512
- args[:relative_path] = true unless args.key?(:relative_path)
513
-
514
- args[:allowed_extensions] ||= []
515
-
516
- if (args[:allowed_extensions].include?("html") || args[:allowed_extensions].include?(:html)) && args[:try_html_extension]
517
- args[:allowed_extensions] << ""
518
- end
519
-
520
- args[:base_path] = "^(?<base_path>#{paths_from_parent}).*$"
521
-
522
- location("*", extensions: args[:allowed_extensions]) do
523
- @middleware[:static_assets] = args
524
- end
525
- end
526
-
527
104
  def file_server(**args)
528
- # Forward to static_assets for implementation
529
- puts "Note: file_server is an alias for static_assets"
105
+ Itsi.log_info "Note: file_server is an alias for static_assets"
530
106
  static_assets(**args)
531
107
  end
532
108
 
@@ -560,7 +136,7 @@ module Itsi
560
136
  when Regexp
561
137
  seg.source
562
138
  else
563
- parts = seg.split('/')
139
+ parts = seg.split("/")
564
140
  parts.map do |part|
565
141
  case part
566
142
  when /^:([A-Za-z_]\w*)(?:\(([^)]*)\))?$/
@@ -603,7 +179,11 @@ module Itsi
603
179
 
604
180
  chain.each do |n|
605
181
  n.middleware.each do |k, v|
606
- merged[k] = v
182
+ merged[k] = if v[:combine]
183
+ ([merged[k] || []] + [v]).flatten
184
+ else
185
+ v
186
+ end
607
187
  end
608
188
  end
609
189
  deep_stringify_keys(merged)