itsi 0.1.11 → 0.1.12

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 (293) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1535 -45
  3. data/{sandbox/itsi_itsi_file/Itsi.rb → Itsi.rb} +19 -13
  4. data/Rakefile +8 -7
  5. data/crates/itsi_error/src/lib.rs +9 -0
  6. data/crates/itsi_rb_helpers/Cargo.toml +1 -0
  7. data/crates/itsi_rb_helpers/src/heap_value.rs +18 -0
  8. data/crates/itsi_rb_helpers/src/lib.rs +34 -7
  9. data/crates/itsi_server/Cargo.toml +69 -30
  10. data/crates/itsi_server/src/lib.rs +79 -147
  11. data/crates/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
  12. data/crates/itsi_server/src/{body_proxy/itsi_body_proxy.rs → ruby_types/itsi_body_proxy/mod.rs} +22 -3
  13. data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
  14. data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
  15. data/crates/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
  16. data/{gems/server/ext/itsi_server/src/request/itsi_request.rs → crates/itsi_server/src/ruby_types/itsi_http_request.rs} +101 -117
  17. data/crates/itsi_server/src/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +72 -41
  18. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  19. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
  20. data/crates/itsi_server/src/ruby_types/itsi_server.rs +82 -0
  21. data/crates/itsi_server/src/ruby_types/mod.rs +55 -0
  22. data/crates/itsi_server/src/server/bind.rs +13 -5
  23. data/crates/itsi_server/src/server/byte_frame.rs +32 -0
  24. data/crates/itsi_server/src/server/cache_store.rs +74 -0
  25. data/crates/itsi_server/src/server/itsi_service.rs +172 -0
  26. data/crates/itsi_server/src/server/lifecycle_event.rs +3 -0
  27. data/crates/itsi_server/src/server/listener.rs +102 -2
  28. data/crates/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
  29. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
  30. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
  31. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
  32. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
  33. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
  34. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
  35. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
  36. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
  37. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
  38. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
  39. data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
  40. data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
  41. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
  42. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  43. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
  44. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
  45. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
  46. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  53. data/crates/itsi_server/src/server/middleware_stack/mod.rs +315 -0
  54. data/crates/itsi_server/src/server/mod.rs +8 -1
  55. data/crates/itsi_server/src/server/process_worker.rs +38 -12
  56. data/crates/itsi_server/src/server/rate_limiter.rs +565 -0
  57. data/crates/itsi_server/src/server/request_job.rs +11 -0
  58. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +119 -42
  59. data/crates/itsi_server/src/server/serve_strategy/mod.rs +9 -6
  60. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +256 -111
  61. data/crates/itsi_server/src/server/signal.rs +19 -0
  62. data/crates/itsi_server/src/server/static_file_server.rs +984 -0
  63. data/crates/itsi_server/src/server/thread_worker.rs +139 -94
  64. data/crates/itsi_server/src/server/types.rs +43 -0
  65. data/crates/itsi_server/test.md +14 -0
  66. data/crates/itsi_tracing/Cargo.toml +1 -0
  67. data/crates/itsi_tracing/src/lib.rs +216 -45
  68. data/docs/.gitignore +7 -0
  69. data/docs/.gitpod.yml +15 -0
  70. data/docs/Itsi.rb +17 -0
  71. data/docs/content/_index.md +17 -0
  72. data/docs/content/about.md +6 -0
  73. data/docs/content/docs/_index.md +18 -0
  74. data/docs/content/docs/first-page.md +9 -0
  75. data/docs/content/docs/folder/_index.md +10 -0
  76. data/docs/content/docs/folder/leaf.md +7 -0
  77. data/docs/go.mod +5 -0
  78. data/docs/go.sum +2 -0
  79. data/docs/hugo.yaml +77 -0
  80. data/examples/static_assets_example.rb +83 -0
  81. data/gems/_index.md +18 -0
  82. data/gems/scheduler/CODE_OF_CONDUCT.md +7 -0
  83. data/gems/scheduler/Cargo.lock +75 -14
  84. data/gems/scheduler/README.md +5 -0
  85. data/gems/scheduler/_index.md +7 -0
  86. data/gems/scheduler/itsi-scheduler.gemspec +4 -1
  87. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  88. data/gems/scheduler/lib/itsi/scheduler.rb +2 -2
  89. data/gems/scheduler/test/test_file_io.rb +0 -1
  90. data/gems/scheduler/test/test_itsi_scheduler.rb +1 -1
  91. data/gems/server/CHANGELOG.md +5 -0
  92. data/gems/server/CODE_OF_CONDUCT.md +7 -0
  93. data/gems/server/Cargo.lock +1536 -45
  94. data/gems/server/README.md +4 -0
  95. data/gems/server/_index.md +6 -0
  96. data/gems/server/exe/itsi +33 -74
  97. data/gems/server/itsi-server.gemspec +3 -2
  98. data/gems/server/lib/itsi/{request.rb → http_request.rb} +29 -5
  99. data/gems/server/lib/itsi/http_response.rb +39 -0
  100. data/gems/server/lib/itsi/server/Itsi.rb +11 -19
  101. data/gems/server/lib/itsi/server/config/dsl.rb +506 -0
  102. data/gems/server/lib/itsi/server/config.rb +103 -8
  103. data/gems/server/lib/itsi/server/default_app/default_app.rb +38 -0
  104. data/gems/server/lib/itsi/server/grpc_interface.rb +213 -0
  105. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +8 -17
  106. data/gems/server/lib/itsi/server/rack_interface.rb +23 -4
  107. data/gems/server/lib/itsi/server/scheduler_interface.rb +1 -1
  108. data/gems/server/lib/itsi/server/scheduler_mode.rb +4 -0
  109. data/gems/server/lib/itsi/server/signal_trap.rb +7 -1
  110. data/gems/server/lib/itsi/server/version.rb +1 -1
  111. data/gems/server/lib/itsi/server.rb +74 -63
  112. data/gems/server/lib/itsi/standard_headers.rb +86 -0
  113. data/gems/server/test/helpers/test_helper.rb +12 -12
  114. data/gems/server/test/test_itsi_server.rb +2 -2
  115. data/lib/itsi/version.rb +1 -1
  116. data/sandbox/itsi_file/Gemfile +11 -0
  117. data/sandbox/itsi_file/Gemfile.lock +69 -0
  118. data/sandbox/itsi_file/Itsi.rb +276 -0
  119. data/sandbox/itsi_file/error.html +2 -0
  120. data/sandbox/itsi_file/organisations_controller.rb +20 -0
  121. data/sandbox/itsi_file/public/assets/image.png +0 -0
  122. data/sandbox/itsi_file/public/assets/index.html +1 -0
  123. data/sandbox/itsi_sandbox_hanami/Gemfile.lock +2 -2
  124. data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
  125. data/sandbox/itsi_sandbox_rack/config.ru +2 -15
  126. data/sandbox/itsi_sandbox_rails/.dockerignore +2 -5
  127. data/sandbox/itsi_sandbox_rails/.github/workflows/ci.yml +1 -1
  128. data/sandbox/itsi_sandbox_rails/.gitignore +2 -1
  129. data/sandbox/itsi_sandbox_rails/Dockerfile +6 -9
  130. data/sandbox/itsi_sandbox_rails/Gemfile +16 -22
  131. data/sandbox/itsi_sandbox_rails/Gemfile.lock +100 -225
  132. data/sandbox/itsi_sandbox_rails/app/assets/config/manifest.js +4 -0
  133. data/sandbox/itsi_sandbox_rails/app/assets/stylesheets/application.css +11 -6
  134. data/sandbox/itsi_sandbox_rails/app/channels/application_cable/channel.rb +4 -0
  135. data/sandbox/itsi_sandbox_rails/app/channels/application_cable/connection.rb +4 -0
  136. data/sandbox/itsi_sandbox_rails/app/controllers/live_controller.rb +7 -8
  137. data/sandbox/itsi_sandbox_rails/app/controllers/uploads_controller.rb +0 -3
  138. data/sandbox/itsi_sandbox_rails/app/views/layouts/application.html.erb +2 -7
  139. data/sandbox/itsi_sandbox_rails/bin/docker-entrypoint +3 -4
  140. data/sandbox/itsi_sandbox_rails/bin/setup +8 -5
  141. data/sandbox/itsi_sandbox_rails/config/application.rb +1 -35
  142. data/sandbox/itsi_sandbox_rails/config/cable.yml +3 -10
  143. data/sandbox/itsi_sandbox_rails/config/credentials.yml.enc +1 -1
  144. data/sandbox/itsi_sandbox_rails/config/database.yml +9 -19
  145. data/sandbox/itsi_sandbox_rails/config/environment.rb +1 -1
  146. data/sandbox/itsi_sandbox_rails/config/environments/development.rb +21 -12
  147. data/sandbox/itsi_sandbox_rails/config/environments/production.rb +49 -34
  148. data/sandbox/itsi_sandbox_rails/config/environments/test.rb +19 -5
  149. data/sandbox/itsi_sandbox_rails/config/initializers/assets.rb +5 -0
  150. data/sandbox/itsi_sandbox_rails/config/initializers/filter_parameter_logging.rb +1 -1
  151. data/sandbox/itsi_sandbox_rails/config/initializers/permissions_policy.rb +13 -0
  152. data/sandbox/itsi_sandbox_rails/config/puma.rb +2 -9
  153. data/sandbox/itsi_sandbox_rails/config.ru +0 -1
  154. data/sandbox/itsi_sandbox_rails/db/migrate/20250301041554_create_posts.rb +1 -1
  155. data/sandbox/itsi_sandbox_rails/db/schema.rb +2 -2
  156. data/sandbox/itsi_sandbox_rails/lib/assets/.keep +0 -0
  157. data/sandbox/itsi_sandbox_rails/public/404.html +66 -113
  158. data/sandbox/itsi_sandbox_rails/public/406-unsupported-browser.html +65 -113
  159. data/sandbox/itsi_sandbox_rails/public/422.html +66 -113
  160. data/sandbox/itsi_sandbox_rails/public/500.html +65 -113
  161. data/sandbox/itsi_sandbox_rails/public/icon.png +0 -0
  162. data/sandbox/itsi_sandbox_rails/public/icon.svg +2 -2
  163. data/sandbox/itsi_sandbox_rails/test/channels/application_cable/connection_test.rb +13 -0
  164. data/sandbox/itsi_sandbox_roda/Gemfile.lock +3 -10
  165. data/tasks.txt +72 -35
  166. metadata +89 -139
  167. data/crates/itsi_server/src/body_proxy/mod.rs +0 -2
  168. data/crates/itsi_server/src/request/itsi_request.rs +0 -298
  169. data/crates/itsi_server/src/request/mod.rs +0 -1
  170. data/crates/itsi_server/src/response/mod.rs +0 -1
  171. data/crates/itsi_server/src/server/itsi_server.rs +0 -288
  172. data/gems/scheduler/ext/itsi_error/Cargo.lock +0 -368
  173. data/gems/scheduler/ext/itsi_error/Cargo.toml +0 -11
  174. data/gems/scheduler/ext/itsi_error/src/from.rs +0 -68
  175. data/gems/scheduler/ext/itsi_error/src/lib.rs +0 -24
  176. data/gems/scheduler/ext/itsi_instrument_entry/Cargo.toml +0 -15
  177. data/gems/scheduler/ext/itsi_instrument_entry/src/lib.rs +0 -31
  178. data/gems/scheduler/ext/itsi_rb_helpers/Cargo.lock +0 -355
  179. data/gems/scheduler/ext/itsi_rb_helpers/Cargo.toml +0 -10
  180. data/gems/scheduler/ext/itsi_rb_helpers/src/heap_value.rs +0 -121
  181. data/gems/scheduler/ext/itsi_rb_helpers/src/lib.rs +0 -201
  182. data/gems/scheduler/ext/itsi_scheduler/Cargo.toml +0 -24
  183. data/gems/scheduler/ext/itsi_scheduler/extconf.rb +0 -6
  184. data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +0 -56
  185. data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +0 -44
  186. data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +0 -44
  187. data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler.rs +0 -308
  188. data/gems/scheduler/ext/itsi_scheduler/src/lib.rs +0 -38
  189. data/gems/scheduler/ext/itsi_server/Cargo.lock +0 -2956
  190. data/gems/scheduler/ext/itsi_server/Cargo.toml +0 -50
  191. data/gems/scheduler/ext/itsi_server/extconf.rb +0 -6
  192. data/gems/scheduler/ext/itsi_server/src/body_proxy/big_bytes.rs +0 -104
  193. data/gems/scheduler/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +0 -122
  194. data/gems/scheduler/ext/itsi_server/src/body_proxy/mod.rs +0 -2
  195. data/gems/scheduler/ext/itsi_server/src/env.rs +0 -43
  196. data/gems/scheduler/ext/itsi_server/src/lib.rs +0 -180
  197. data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +0 -298
  198. data/gems/scheduler/ext/itsi_server/src/request/mod.rs +0 -1
  199. data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +0 -357
  200. data/gems/scheduler/ext/itsi_server/src/response/mod.rs +0 -1
  201. data/gems/scheduler/ext/itsi_server/src/server/bind.rs +0 -174
  202. data/gems/scheduler/ext/itsi_server/src/server/bind_protocol.rs +0 -37
  203. data/gems/scheduler/ext/itsi_server/src/server/io_stream.rs +0 -104
  204. data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +0 -288
  205. data/gems/scheduler/ext/itsi_server/src/server/lifecycle_event.rs +0 -9
  206. data/gems/scheduler/ext/itsi_server/src/server/listener.rs +0 -318
  207. data/gems/scheduler/ext/itsi_server/src/server/mod.rs +0 -11
  208. data/gems/scheduler/ext/itsi_server/src/server/process_worker.rs +0 -203
  209. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +0 -260
  210. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/mod.rs +0 -27
  211. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +0 -276
  212. data/gems/scheduler/ext/itsi_server/src/server/signal.rs +0 -74
  213. data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +0 -399
  214. data/gems/scheduler/ext/itsi_server/src/server/tls/locked_dir_cache.rs +0 -132
  215. data/gems/scheduler/ext/itsi_server/src/server/tls.rs +0 -265
  216. data/gems/scheduler/ext/itsi_tracing/Cargo.lock +0 -274
  217. data/gems/scheduler/ext/itsi_tracing/Cargo.toml +0 -16
  218. data/gems/scheduler/ext/itsi_tracing/src/lib.rs +0 -58
  219. data/gems/server/ext/itsi_error/Cargo.lock +0 -368
  220. data/gems/server/ext/itsi_error/Cargo.toml +0 -11
  221. data/gems/server/ext/itsi_error/src/from.rs +0 -68
  222. data/gems/server/ext/itsi_error/src/lib.rs +0 -24
  223. data/gems/server/ext/itsi_instrument_entry/Cargo.toml +0 -15
  224. data/gems/server/ext/itsi_instrument_entry/src/lib.rs +0 -31
  225. data/gems/server/ext/itsi_rb_helpers/Cargo.lock +0 -355
  226. data/gems/server/ext/itsi_rb_helpers/Cargo.toml +0 -10
  227. data/gems/server/ext/itsi_rb_helpers/src/heap_value.rs +0 -121
  228. data/gems/server/ext/itsi_rb_helpers/src/lib.rs +0 -201
  229. data/gems/server/ext/itsi_scheduler/Cargo.toml +0 -24
  230. data/gems/server/ext/itsi_scheduler/extconf.rb +0 -6
  231. data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +0 -56
  232. data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +0 -44
  233. data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +0 -44
  234. data/gems/server/ext/itsi_scheduler/src/itsi_scheduler.rs +0 -308
  235. data/gems/server/ext/itsi_scheduler/src/lib.rs +0 -38
  236. data/gems/server/ext/itsi_server/Cargo.lock +0 -2956
  237. data/gems/server/ext/itsi_server/Cargo.toml +0 -50
  238. data/gems/server/ext/itsi_server/extconf.rb +0 -6
  239. data/gems/server/ext/itsi_server/src/body_proxy/big_bytes.rs +0 -104
  240. data/gems/server/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +0 -122
  241. data/gems/server/ext/itsi_server/src/body_proxy/mod.rs +0 -2
  242. data/gems/server/ext/itsi_server/src/env.rs +0 -43
  243. data/gems/server/ext/itsi_server/src/lib.rs +0 -180
  244. data/gems/server/ext/itsi_server/src/request/mod.rs +0 -1
  245. data/gems/server/ext/itsi_server/src/response/itsi_response.rs +0 -357
  246. data/gems/server/ext/itsi_server/src/response/mod.rs +0 -1
  247. data/gems/server/ext/itsi_server/src/server/bind.rs +0 -174
  248. data/gems/server/ext/itsi_server/src/server/bind_protocol.rs +0 -37
  249. data/gems/server/ext/itsi_server/src/server/io_stream.rs +0 -104
  250. data/gems/server/ext/itsi_server/src/server/itsi_server.rs +0 -288
  251. data/gems/server/ext/itsi_server/src/server/lifecycle_event.rs +0 -9
  252. data/gems/server/ext/itsi_server/src/server/listener.rs +0 -318
  253. data/gems/server/ext/itsi_server/src/server/mod.rs +0 -11
  254. data/gems/server/ext/itsi_server/src/server/process_worker.rs +0 -203
  255. data/gems/server/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +0 -260
  256. data/gems/server/ext/itsi_server/src/server/serve_strategy/mod.rs +0 -27
  257. data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +0 -276
  258. data/gems/server/ext/itsi_server/src/server/signal.rs +0 -74
  259. data/gems/server/ext/itsi_server/src/server/thread_worker.rs +0 -399
  260. data/gems/server/ext/itsi_server/src/server/tls/locked_dir_cache.rs +0 -132
  261. data/gems/server/ext/itsi_server/src/server/tls.rs +0 -265
  262. data/gems/server/ext/itsi_tracing/Cargo.lock +0 -274
  263. data/gems/server/ext/itsi_tracing/Cargo.toml +0 -16
  264. data/gems/server/ext/itsi_tracing/src/lib.rs +0 -58
  265. data/gems/server/lib/itsi/server/options_dsl.rb +0 -401
  266. data/gems/server/lib/itsi/stream_io.rb +0 -38
  267. data/location_dsl.rb +0 -381
  268. data/sandbox/itsi_sandbox_rails/.kamal/hooks/docker-setup.sample +0 -3
  269. data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-app-boot.sample +0 -3
  270. data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-deploy.sample +0 -14
  271. data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-proxy-reboot.sample +0 -3
  272. data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-app-boot.sample +0 -3
  273. data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-build.sample +0 -51
  274. data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-connect.sample +0 -47
  275. data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-deploy.sample +0 -109
  276. data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-proxy-reboot.sample +0 -3
  277. data/sandbox/itsi_sandbox_rails/.kamal/secrets +0 -17
  278. data/sandbox/itsi_sandbox_rails/bin/dev +0 -2
  279. data/sandbox/itsi_sandbox_rails/bin/jobs +0 -6
  280. data/sandbox/itsi_sandbox_rails/bin/kamal +0 -27
  281. data/sandbox/itsi_sandbox_rails/bin/thrust +0 -5
  282. data/sandbox/itsi_sandbox_rails/config/cache.yml +0 -16
  283. data/sandbox/itsi_sandbox_rails/config/deploy.yml +0 -116
  284. data/sandbox/itsi_sandbox_rails/config/queue.yml +0 -18
  285. data/sandbox/itsi_sandbox_rails/config/recurring.yml +0 -10
  286. data/sandbox/itsi_sandbox_rails/db/cable_schema.rb +0 -11
  287. data/sandbox/itsi_sandbox_rails/db/cache_schema.rb +0 -14
  288. data/sandbox/itsi_sandbox_rails/db/queue_schema.rb +0 -129
  289. data/sandbox/itsi_sandbox_rails/public/400.html +0 -114
  290. data/sandbox/itsi_sandbox_rails/test/fixtures/posts.yml +0 -9
  291. data/sandbox/itsi_sandbox_rails/test/models/post_test.rb +0 -7
  292. /data/{sandbox/itsi_sandbox_rails/script/.keep → crates/_index.md} +0 -0
  293. /data/gems/server/lib/itsi/{index.html → server/default_app/index.html} +0 -0
@@ -0,0 +1,300 @@
1
+ use super::{
2
+ header_interpretation::{find_first_supported, header_contains},
3
+ FromValue, MiddlewareLayer,
4
+ };
5
+ use crate::server::{
6
+ itsi_service::RequestContext,
7
+ types::{HttpRequest, HttpResponse},
8
+ };
9
+ use async_compression::{
10
+ tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder},
11
+ Level,
12
+ };
13
+ use async_trait::async_trait;
14
+ use bytes::{Bytes, BytesMut};
15
+ use either::Either;
16
+ use futures::TryStreamExt;
17
+ use http::{
18
+ header::{GetAll, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE},
19
+ HeaderValue, Response,
20
+ };
21
+ use http_body_util::{combinators::BoxBody, BodyExt, Full, StreamBody};
22
+ use hyper::body::{Body, Frame};
23
+ use magnus::error::Result;
24
+ use serde::{Deserialize, Serialize};
25
+ use std::convert::Infallible;
26
+ use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
27
+ use tokio_stream::StreamExt;
28
+ use tokio_util::io::{ReaderStream, StreamReader};
29
+ #[derive(Debug, Clone, Serialize, Deserialize)]
30
+ pub struct Compression {
31
+ min_size: usize,
32
+ algorithms: Vec<CompressionAlgorithm>,
33
+ compress_streams: bool,
34
+ mime_types: Vec<MimeType>,
35
+ level: CompressionLevel,
36
+ }
37
+
38
+ #[derive(Debug, Clone, Serialize, Deserialize)]
39
+ enum CompressionLevel {
40
+ #[serde(rename(deserialize = "fastest"))]
41
+ Fastest,
42
+ #[serde(rename(deserialize = "best"))]
43
+ Best,
44
+ #[serde(rename(deserialize = "default"))]
45
+ Default,
46
+ #[serde(rename(deserialize = "precise"))]
47
+ Precise(i32),
48
+ }
49
+
50
+ impl CompressionLevel {
51
+ fn to_async_compression_level(&self) -> Level {
52
+ match self {
53
+ CompressionLevel::Fastest => Level::Fastest,
54
+ CompressionLevel::Best => Level::Best,
55
+ CompressionLevel::Default => Level::Default,
56
+ CompressionLevel::Precise(level) => Level::Precise(*level),
57
+ }
58
+ }
59
+ }
60
+
61
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
62
+ pub enum CompressionAlgorithm {
63
+ #[serde(rename(deserialize = "gzip"))]
64
+ Gzip,
65
+ #[serde(rename(deserialize = "brotli"))]
66
+ Brotli,
67
+ #[serde(rename(deserialize = "deflate"))]
68
+ Deflate,
69
+ #[serde(rename(deserialize = "zstd"))]
70
+ Zstd,
71
+ #[serde(rename(deserialize = "none"))]
72
+ None,
73
+ }
74
+
75
+ impl CompressionAlgorithm {
76
+ pub fn as_str(&self) -> &'static str {
77
+ match self {
78
+ CompressionAlgorithm::Gzip => "gzip",
79
+ CompressionAlgorithm::Brotli => "br",
80
+ CompressionAlgorithm::Deflate => "deflate",
81
+ CompressionAlgorithm::Zstd => "zstd",
82
+ CompressionAlgorithm::None => "none",
83
+ }
84
+ }
85
+
86
+ pub fn header_value(&self) -> HeaderValue {
87
+ HeaderValue::from_str(self.as_str()).unwrap()
88
+ }
89
+ }
90
+
91
+ #[derive(Debug, Clone, Serialize, Deserialize)]
92
+ enum MimeType {
93
+ #[serde(rename(deserialize = "text"))]
94
+ Text,
95
+ #[serde(rename(deserialize = "image"))]
96
+ Image,
97
+ #[serde(rename(deserialize = "application"))]
98
+ Application,
99
+ #[serde(rename(deserialize = "audio"))]
100
+ Audio,
101
+ #[serde(rename(deserialize = "video"))]
102
+ Video,
103
+ #[serde(rename(deserialize = "other"))]
104
+ Other(String),
105
+ #[serde(rename(deserialize = "all"))]
106
+ All,
107
+ }
108
+
109
+ impl MimeType {
110
+ pub fn matches(&self, content_encodings: &GetAll<HeaderValue>) -> bool {
111
+ match self {
112
+ MimeType::Text => header_contains(content_encodings, "text/*"),
113
+ MimeType::Image => header_contains(content_encodings, "image/*"),
114
+ MimeType::Application => header_contains(content_encodings, "application/*"),
115
+ MimeType::Audio => header_contains(content_encodings, "audio/*"),
116
+ MimeType::Video => header_contains(content_encodings, "video/*"),
117
+ MimeType::Other(v) => header_contains(content_encodings, v),
118
+ MimeType::All => header_contains(content_encodings, "*"),
119
+ }
120
+ }
121
+ }
122
+
123
+ fn stream_encode<R>(encoder: R) -> BoxBody<Bytes, Infallible>
124
+ where
125
+ R: AsyncRead + Unpin + Sync + Send + 'static,
126
+ {
127
+ let encoded_stream = ReaderStream::new(encoder).map(|res| {
128
+ res.map(Frame::data)
129
+ .map_err(|_| -> Infallible { unreachable!("We handle IO errors above") })
130
+ });
131
+ BoxBody::new(StreamBody::new(encoded_stream))
132
+ }
133
+
134
+ fn update_content_encoding(parts: &mut http::response::Parts, new_encoding: HeaderValue) {
135
+ if let Some(existing) = parts.headers.get(CONTENT_ENCODING) {
136
+ let mut encodings = existing.to_str().unwrap_or("").to_owned();
137
+ if !encodings.is_empty() {
138
+ encodings.push_str(", ");
139
+ }
140
+ encodings.push_str(new_encoding.to_str().unwrap());
141
+ parts
142
+ .headers
143
+ .insert(CONTENT_ENCODING, HeaderValue::from_str(&encodings).unwrap());
144
+ } else {
145
+ parts.headers.insert(CONTENT_ENCODING, new_encoding);
146
+ }
147
+ }
148
+
149
+ #[async_trait]
150
+ impl MiddlewareLayer for Compression {
151
+ /// A the request comes in, take note of the accepted content encodings,
152
+ /// so that we can apply compression on the response, where appropriate.
153
+ ///
154
+ /// We store the temporary state inside the RequestContext.
155
+ async fn before(
156
+ &self,
157
+ req: HttpRequest,
158
+ context: &mut RequestContext,
159
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
160
+ let algo = match find_first_supported(
161
+ &req.headers().get_all(ACCEPT_ENCODING),
162
+ self.algorithms.iter().map(|algo| algo.as_str()),
163
+ ) {
164
+ Some("gzip") => CompressionAlgorithm::Gzip,
165
+ Some("br") => CompressionAlgorithm::Brotli,
166
+ Some("deflate") => CompressionAlgorithm::Deflate,
167
+ Some("zstd") => CompressionAlgorithm::Zstd,
168
+ _ => CompressionAlgorithm::None,
169
+ };
170
+
171
+ if matches!(algo, CompressionAlgorithm::None) {
172
+ return Ok(Either::Left(req));
173
+ }
174
+
175
+ context.set_compression_method(algo);
176
+
177
+ Ok(Either::Left(req))
178
+ }
179
+
180
+ /// We'll apply compression on the response, where appropriate.
181
+ /// This is if:
182
+ /// * The response body is larger than the minimum size.
183
+ /// * The response content type is supported.
184
+ /// * The client supports the compression algorithm.
185
+ async fn after(&self, resp: HttpResponse, context: &mut RequestContext) -> HttpResponse {
186
+ let compression_method;
187
+ if let Some(method) = context.compression_method.get() {
188
+ compression_method = method.clone();
189
+ } else {
190
+ return resp;
191
+ }
192
+
193
+ if matches!(compression_method, CompressionAlgorithm::None) {
194
+ return resp;
195
+ }
196
+
197
+ let body_size = resp.size_hint().exact();
198
+ let resp = resp;
199
+
200
+ if !self
201
+ .mime_types
202
+ .iter()
203
+ .any(|mt| mt.matches(&resp.headers().get_all(CONTENT_TYPE)))
204
+ {
205
+ return resp;
206
+ }
207
+
208
+ if body_size.is_none() && !self.compress_streams {
209
+ return resp;
210
+ }
211
+
212
+ if body_size.is_some_and(|s| s < self.min_size as u64) {
213
+ return resp;
214
+ }
215
+
216
+ let (mut parts, body) = resp.into_parts();
217
+
218
+ let new_body = if let Some(_size) = body_size {
219
+ let full_bytes: Bytes = body
220
+ .into_data_stream()
221
+ .try_fold(BytesMut::new(), |mut acc, chunk| async move {
222
+ acc.extend_from_slice(&chunk);
223
+ Ok(acc)
224
+ })
225
+ .await
226
+ .unwrap()
227
+ .freeze();
228
+
229
+ let cursor = std::io::Cursor::new(full_bytes);
230
+ let reader = BufReader::new(cursor);
231
+ let compressed_bytes = match compression_method {
232
+ CompressionAlgorithm::Gzip => {
233
+ let mut encoder =
234
+ GzipEncoder::with_quality(reader, self.level.to_async_compression_level());
235
+ let mut buf = Vec::new();
236
+ encoder.read_to_end(&mut buf).await.unwrap();
237
+ buf
238
+ }
239
+ CompressionAlgorithm::Brotli => {
240
+ let mut encoder = BrotliEncoder::with_quality(
241
+ reader,
242
+ self.level.to_async_compression_level(),
243
+ );
244
+ let mut buf = Vec::new();
245
+ encoder.read_to_end(&mut buf).await.unwrap();
246
+ buf
247
+ }
248
+ CompressionAlgorithm::Deflate => {
249
+ let mut encoder = DeflateEncoder::with_quality(
250
+ reader,
251
+ self.level.to_async_compression_level(),
252
+ );
253
+ let mut buf = Vec::new();
254
+ encoder.read_to_end(&mut buf).await.unwrap();
255
+ buf
256
+ }
257
+ CompressionAlgorithm::Zstd => {
258
+ let mut encoder =
259
+ ZstdEncoder::with_quality(reader, self.level.to_async_compression_level());
260
+ let mut buf = Vec::new();
261
+ encoder.read_to_end(&mut buf).await.unwrap();
262
+ buf
263
+ }
264
+ CompressionAlgorithm::None => unreachable!(),
265
+ };
266
+ BoxBody::new(Full::new(Bytes::from(compressed_bytes)))
267
+ } else {
268
+ let stream = body
269
+ .into_data_stream()
270
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e));
271
+ let async_read_fut = StreamReader::new(stream);
272
+ let reader = BufReader::new(async_read_fut);
273
+ match compression_method {
274
+ CompressionAlgorithm::Gzip => stream_encode(GzipEncoder::with_quality(
275
+ reader,
276
+ self.level.to_async_compression_level(),
277
+ )),
278
+ CompressionAlgorithm::Brotli => stream_encode(BrotliEncoder::with_quality(
279
+ reader,
280
+ self.level.to_async_compression_level(),
281
+ )),
282
+ CompressionAlgorithm::Deflate => stream_encode(DeflateEncoder::with_quality(
283
+ reader,
284
+ self.level.to_async_compression_level(),
285
+ )),
286
+ CompressionAlgorithm::Zstd => stream_encode(ZstdEncoder::with_quality(
287
+ reader,
288
+ self.level.to_async_compression_level(),
289
+ )),
290
+ CompressionAlgorithm::None => unreachable!(),
291
+ }
292
+ };
293
+
294
+ update_content_encoding(&mut parts, compression_method.header_value());
295
+ parts.headers.remove(CONTENT_LENGTH);
296
+
297
+ Response::from_parts(parts, new_body)
298
+ }
299
+ }
300
+ impl FromValue for Compression {}
@@ -0,0 +1,287 @@
1
+ use super::{FromValue, MiddlewareLayer};
2
+ use crate::server::{
3
+ itsi_service::RequestContext,
4
+ types::{HttpRequest, HttpResponse, RequestExt},
5
+ };
6
+ use async_trait::async_trait;
7
+ use http::{HeaderMap, Method, Response};
8
+ use http_body_util::{combinators::BoxBody, Empty};
9
+ use itsi_error::ItsiError;
10
+ use magnus::error::Result;
11
+ use serde::Deserialize;
12
+
13
+ #[derive(Debug, Clone, Deserialize)]
14
+ pub struct Cors {
15
+ pub allowed_origins: Vec<String>,
16
+ pub allowed_methods: Vec<HttpMethod>,
17
+ pub allowed_headers: Vec<String>,
18
+ pub exposed_headers: Vec<String>,
19
+ pub allow_credentials: bool,
20
+ pub max_age: Option<u64>,
21
+ }
22
+
23
+ #[derive(Debug, Clone, Deserialize)]
24
+ pub enum HttpMethod {
25
+ #[serde(rename(deserialize = "GET"))]
26
+ Get,
27
+ #[serde(rename(deserialize = "POST"))]
28
+ Post,
29
+ #[serde(rename(deserialize = "PUT"))]
30
+ Put,
31
+ #[serde(rename(deserialize = "DELETE"))]
32
+ Delete,
33
+ #[serde(rename(deserialize = "OPTIONS"))]
34
+ Options,
35
+ #[serde(rename(deserialize = "HEAD"))]
36
+ Head,
37
+ #[serde(rename(deserialize = "PATCH"))]
38
+ Patch,
39
+ }
40
+
41
+ impl HttpMethod {
42
+ pub fn matches(&self, other: &str) -> bool {
43
+ match self {
44
+ HttpMethod::Get => other.eq_ignore_ascii_case("GET"),
45
+ HttpMethod::Post => other.eq_ignore_ascii_case("POST"),
46
+ HttpMethod::Put => other.eq_ignore_ascii_case("PUT"),
47
+ HttpMethod::Delete => other.eq_ignore_ascii_case("DELETE"),
48
+ HttpMethod::Options => other.eq_ignore_ascii_case("OPTIONS"),
49
+ HttpMethod::Head => other.eq_ignore_ascii_case("HEAD"),
50
+ HttpMethod::Patch => other.eq_ignore_ascii_case("PATCH"),
51
+ }
52
+ }
53
+
54
+ pub fn to_str(&self) -> &str {
55
+ match self {
56
+ HttpMethod::Get => "GET",
57
+ HttpMethod::Post => "POST",
58
+ HttpMethod::Put => "PUT",
59
+ HttpMethod::Delete => "DELETE",
60
+ HttpMethod::Options => "OPTIONS",
61
+ HttpMethod::Head => "HEAD",
62
+ HttpMethod::Patch => "PATCH",
63
+ }
64
+ }
65
+ }
66
+
67
+ impl Cors {
68
+ /// Generate the simple CORS headers (used in normal responses)
69
+ fn cors_headers(&self, origin: &str) -> Result<HeaderMap> {
70
+ let mut headers = HeaderMap::new();
71
+
72
+ headers.insert("Vary", "Origin".parse().map_err(ItsiError::default)?);
73
+
74
+ if origin.is_empty() {
75
+ // When credentials are allowed, you cannot return "*".
76
+ if !self.allow_credentials {
77
+ headers.insert(
78
+ "Access-Control-Allow-Origin",
79
+ "*".parse().map_err(ItsiError::default)?,
80
+ );
81
+ }
82
+ return Ok(headers);
83
+ }
84
+
85
+ // Only return a header if the origin is allowed.
86
+ if self.allowed_origins.iter().any(|o| o == origin || o == "*") {
87
+ // If credentials are allowed, we must echo back the exact origin.
88
+ let value = if self.allow_credentials {
89
+ origin
90
+ } else {
91
+ // If not, and if "*" is allowed, you can still use "*".
92
+ if self.allowed_origins.iter().any(|o| o == "*") {
93
+ "*"
94
+ } else {
95
+ origin
96
+ }
97
+ };
98
+ headers.insert(
99
+ "Access-Control-Allow-Origin",
100
+ value.parse().map_err(ItsiError::default)?,
101
+ );
102
+ }
103
+
104
+ if !self.allowed_methods.is_empty() {
105
+ headers.insert(
106
+ "Access-Control-Allow-Methods",
107
+ self.allowed_methods
108
+ .iter()
109
+ .map(HttpMethod::to_str)
110
+ .collect::<Vec<&str>>()
111
+ .join(", ")
112
+ .parse()
113
+ .map_err(ItsiError::default)?,
114
+ );
115
+ }
116
+ if !self.allowed_headers.is_empty() {
117
+ headers.insert(
118
+ "Access-Control-Allow-Headers",
119
+ self.allowed_headers
120
+ .join(", ")
121
+ .parse()
122
+ .map_err(ItsiError::default)?,
123
+ );
124
+ }
125
+ if self.allow_credentials {
126
+ headers.insert(
127
+ "Access-Control-Allow-Credentials",
128
+ "true".parse().map_err(ItsiError::default)?,
129
+ );
130
+ }
131
+ if let Some(max_age) = self.max_age {
132
+ headers.insert(
133
+ "Access-Control-Max-Age",
134
+ max_age.to_string().parse().map_err(ItsiError::default)?,
135
+ );
136
+ }
137
+ if !self.exposed_headers.is_empty() {
138
+ headers.insert(
139
+ "Access-Control-Expose-Headers",
140
+ self.exposed_headers
141
+ .join(", ")
142
+ .parse()
143
+ .map_err(ItsiError::default)?,
144
+ );
145
+ }
146
+ Ok(headers)
147
+ }
148
+
149
+ fn preflight_headers(
150
+ &self,
151
+ origin: Option<&str>,
152
+ req_method: Option<&str>,
153
+ req_headers: Option<&str>,
154
+ ) -> Result<HeaderMap> {
155
+ let mut headers = HeaderMap::new();
156
+
157
+ headers.insert("Vary", "Origin".parse().map_err(ItsiError::default)?);
158
+
159
+ let origin = match origin {
160
+ Some(o) if !o.is_empty() => o,
161
+ _ => return Ok(headers), // Missing Origin – preflight fails
162
+ };
163
+
164
+ if !self
165
+ .allowed_origins
166
+ .iter()
167
+ .any(|allowed| allowed == "*" || allowed == origin)
168
+ {
169
+ return Ok(headers);
170
+ }
171
+
172
+ let request_method = match req_method {
173
+ Some(m) if !m.is_empty() => m,
174
+ _ => return Ok(headers), // Missing request method – preflight fails
175
+ };
176
+
177
+ if !self
178
+ .allowed_methods
179
+ .iter()
180
+ .any(|m| m.matches(request_method))
181
+ {
182
+ return Ok(headers);
183
+ }
184
+
185
+ if let Some(request_headers) = req_headers {
186
+ let req_headers_list: Vec<&str> = request_headers
187
+ .split(',')
188
+ .map(|s| s.trim())
189
+ .filter(|s| !s.is_empty())
190
+ .collect();
191
+ for header in req_headers_list {
192
+ if !self
193
+ .allowed_headers
194
+ .iter()
195
+ .any(|allowed| allowed.eq_ignore_ascii_case(header))
196
+ {
197
+ return Ok(headers);
198
+ }
199
+ }
200
+ }
201
+
202
+ headers.insert("Access-Control-Allow-Origin", origin.parse().unwrap());
203
+ headers.insert(
204
+ "Access-Control-Allow-Methods",
205
+ self.allowed_methods
206
+ .iter()
207
+ .map(HttpMethod::to_str)
208
+ .collect::<Vec<&str>>()
209
+ .join(", ")
210
+ .parse()
211
+ .map_err(ItsiError::default)?,
212
+ );
213
+ headers.insert(
214
+ "Access-Control-Allow-Headers",
215
+ self.allowed_headers
216
+ .join(", ")
217
+ .parse()
218
+ .map_err(ItsiError::default)?,
219
+ );
220
+ if self.allow_credentials {
221
+ headers.insert(
222
+ "Access-Control-Allow-Credentials",
223
+ "true".parse().map_err(ItsiError::default)?,
224
+ );
225
+ }
226
+ if let Some(max_age) = self.max_age {
227
+ headers.insert(
228
+ "Access-Control-Max-Age",
229
+ max_age.to_string().parse().map_err(ItsiError::default)?,
230
+ );
231
+ }
232
+ if !self.exposed_headers.is_empty() {
233
+ headers.insert(
234
+ "Access-Control-Expose-Headers",
235
+ self.exposed_headers
236
+ .join(", ")
237
+ .parse()
238
+ .map_err(ItsiError::default)?,
239
+ );
240
+ }
241
+
242
+ Ok(headers)
243
+ }
244
+ }
245
+
246
+ #[async_trait]
247
+ impl MiddlewareLayer for Cors {
248
+ // For OPTIONS (preflight) requests we:
249
+ // 1. Extract Origin, Access-Control-Request-Method, and Access-Control-Request-Headers.
250
+ // 2. Validate them using our hardened preflight_headers function.
251
+ // 3. If validations pass (i.e. headers is non-empty), return a 204 response with those headers.
252
+ // Otherwise, the absence of headers indicates the request doesn’t meet the CORS policy.
253
+ async fn before(
254
+ &self,
255
+ req: HttpRequest,
256
+ context: &mut RequestContext,
257
+ ) -> Result<either::Either<HttpRequest, HttpResponse>> {
258
+ let origin = req.header("Origin");
259
+ if req.method() == Method::OPTIONS {
260
+ let ac_request_method = req.header("Access-Control-Request-Method");
261
+ let ac_request_headers = req.header("Access-Control-Request-Headers");
262
+ let headers = self.preflight_headers(origin, ac_request_method, ac_request_headers)?;
263
+
264
+ let mut response_builder = Response::builder().status(204);
265
+ *response_builder.headers_mut().unwrap() = headers;
266
+ let response = response_builder
267
+ .body(BoxBody::new(Empty::new()))
268
+ .map_err(ItsiError::default)?;
269
+ return Ok(either::Either::Right(response));
270
+ }
271
+ context.set_origin(origin.map(|s| s.to_string()));
272
+ Ok(either::Either::Left(req))
273
+ }
274
+
275
+ // The after hook can be used to inject CORS headers into non-preflight responses.
276
+ async fn after(&self, mut resp: HttpResponse, context: &mut RequestContext) -> HttpResponse {
277
+ if let Some(Some(origin)) = context.origin.get() {
278
+ if let Ok(cors_headers) = self.cors_headers(origin) {
279
+ for (key, value) in cors_headers.iter() {
280
+ resp.headers_mut().insert(key.clone(), value.clone());
281
+ }
282
+ }
283
+ }
284
+ resp
285
+ }
286
+ }
287
+ impl FromValue for Cors {}
@@ -0,0 +1,48 @@
1
+ use crate::server::{
2
+ itsi_service::RequestContext,
3
+ types::{HttpRequest, HttpResponse},
4
+ };
5
+
6
+ use super::{ErrorResponse, FromValue, MiddlewareLayer};
7
+ use async_trait::async_trait;
8
+ use either::Either;
9
+ use itsi_error::ItsiError;
10
+ use magnus::error::Result;
11
+ use regex::RegexSet;
12
+ use serde::Deserialize;
13
+ use std::sync::OnceLock;
14
+
15
+ #[derive(Debug, Clone, Deserialize)]
16
+ pub struct DenyList {
17
+ #[serde(skip_deserializing)]
18
+ pub denied_ips: OnceLock<RegexSet>,
19
+ pub denied_patterns: Vec<String>,
20
+ pub error_response: ErrorResponse,
21
+ }
22
+
23
+ #[async_trait]
24
+ impl MiddlewareLayer for DenyList {
25
+ async fn initialize(&self) -> Result<()> {
26
+ let denied_ips = RegexSet::new(&self.denied_patterns).map_err(ItsiError::default)?;
27
+ self.denied_ips
28
+ .set(denied_ips)
29
+ .map_err(|e| ItsiError::default(format!("Failed to set allowed IPs: {:?}", e)))?;
30
+ Ok(())
31
+ }
32
+
33
+ async fn before(
34
+ &self,
35
+ req: HttpRequest,
36
+ context: &mut RequestContext,
37
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
38
+ if let Some(denied_ips) = self.denied_ips.get() {
39
+ if denied_ips.is_match(&context.addr) {
40
+ return Ok(Either::Right(
41
+ self.error_response.to_http_response(&req).await,
42
+ ));
43
+ }
44
+ }
45
+ Ok(Either::Left(req))
46
+ }
47
+ }
48
+ impl FromValue for DenyList {}