itsi 0.1.20 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -8
  3. data/Cargo.lock +2 -2
  4. data/LICENSE.txt +698 -0
  5. data/README.md +15 -4
  6. data/Rakefile +9 -5
  7. data/crates/itsi_acme/.gitignore +4 -0
  8. data/crates/itsi_acme/Cargo.toml +86 -0
  9. data/crates/itsi_acme/LICENSE-APACHE +201 -0
  10. data/crates/itsi_acme/LICENSE-MIT +23 -0
  11. data/crates/itsi_acme/README.md +9 -0
  12. data/crates/itsi_acme/examples/high_level.rs +63 -0
  13. data/crates/itsi_acme/examples/high_level_warp.rs +52 -0
  14. data/crates/itsi_acme/examples/low_level.rs +87 -0
  15. data/crates/itsi_acme/examples/low_level_axum.rs +66 -0
  16. data/crates/itsi_acme/src/acceptor.rs +81 -0
  17. data/crates/itsi_acme/src/acme.rs +354 -0
  18. data/crates/itsi_acme/src/axum.rs +86 -0
  19. data/crates/itsi_acme/src/cache.rs +39 -0
  20. data/crates/itsi_acme/src/caches/boxed.rs +80 -0
  21. data/crates/itsi_acme/src/caches/composite.rs +69 -0
  22. data/crates/itsi_acme/src/caches/dir.rs +106 -0
  23. data/crates/itsi_acme/src/caches/mod.rs +11 -0
  24. data/crates/itsi_acme/src/caches/no.rs +78 -0
  25. data/crates/itsi_acme/src/caches/test.rs +136 -0
  26. data/crates/itsi_acme/src/config.rs +172 -0
  27. data/crates/itsi_acme/src/https_helper.rs +69 -0
  28. data/crates/itsi_acme/src/incoming.rs +142 -0
  29. data/crates/itsi_acme/src/jose.rs +161 -0
  30. data/crates/itsi_acme/src/lib.rs +142 -0
  31. data/crates/itsi_acme/src/resolver.rs +59 -0
  32. data/crates/itsi_acme/src/state.rs +424 -0
  33. data/crates/itsi_rb_helpers/src/lib.rs +4 -3
  34. data/crates/itsi_scheduler/Cargo.toml +1 -1
  35. data/crates/itsi_scheduler/src/itsi_scheduler.rs +8 -2
  36. data/crates/itsi_scheduler/src/lib.rs +1 -0
  37. data/crates/itsi_server/Cargo.toml +1 -1
  38. data/crates/itsi_server/src/lib.rs +2 -1
  39. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +18 -1
  40. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +11 -3
  41. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +122 -63
  42. data/crates/itsi_server/src/ruby_types/itsi_server.rs +2 -0
  43. data/crates/itsi_server/src/server/binds/bind.rs +3 -0
  44. data/crates/itsi_server/src/server/binds/listener.rs +12 -5
  45. data/crates/itsi_server/src/server/binds/tls.rs +13 -5
  46. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +12 -5
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +8 -1
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +9 -1
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +48 -43
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +11 -2
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +39 -12
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +36 -27
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +25 -11
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +12 -3
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +74 -72
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +15 -1
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +11 -8
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +19 -11
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +5 -5
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +2 -2
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +11 -5
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +17 -20
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +19 -8
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +16 -37
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +22 -12
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +26 -11
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +7 -1
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +14 -4
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +19 -0
  70. data/crates/itsi_server/src/server/middleware_stack/mod.rs +49 -13
  71. data/crates/itsi_server/src/server/mod.rs +1 -0
  72. data/crates/itsi_server/src/server/redirect_type.rs +26 -0
  73. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +22 -16
  74. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +49 -12
  75. data/crates/itsi_server/src/server/signal.rs +1 -0
  76. data/crates/itsi_server/src/server/size_limited_incoming.rs +6 -0
  77. data/crates/itsi_server/src/server/thread_worker.rs +5 -1
  78. data/crates/itsi_server/src/services/itsi_http_service.rs +20 -2
  79. data/crates/itsi_server/src/services/rate_limiter.rs +15 -4
  80. data/crates/itsi_server/src/services/static_file_server.rs +33 -19
  81. data/crates/itsi_tracing/src/lib.rs +42 -22
  82. data/docs/content/_index.md +1 -2
  83. data/docs/content/acknowledgements/_index.md +5 -2
  84. data/docs/content/configuration/_index.md +8 -5
  85. data/docs/content/contact/_index.md +8 -1
  86. data/docs/content/faqs/_index.md +5 -3
  87. data/docs/content/features/_index.md +56 -50
  88. data/docs/content/getting_started/_index.md +8 -5
  89. data/docs/content/getting_started/local_development.md +68 -8
  90. data/docs/content/getting_started/logging.md +16 -9
  91. data/docs/content/getting_started/running_itsi_in_production.md +5 -3
  92. data/docs/content/getting_started/signals.md +38 -0
  93. data/docs/content/itsi_scheduler/_index.md +8 -7
  94. data/docs/content/utilities/_index.md +13 -0
  95. data/docs/content/utilities/config_file_testing.md +17 -0
  96. data/docs/content/utilities/passfile_generator.md +41 -0
  97. data/docs/content/utilities/route_testing.md +27 -0
  98. data/docs/content/utilities/secrets_management.md +30 -0
  99. data/docs/hugo.yaml +1 -1
  100. data/fairytale.txt +3 -4
  101. data/gems/scheduler/Cargo.lock +1 -1
  102. data/gems/scheduler/README.md +4 -5
  103. data/gems/scheduler/Rakefile +0 -4
  104. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  105. data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
  106. data/gems/scheduler/test/test_active_record.rb +12 -7
  107. data/gems/server/Cargo.lock +1 -1
  108. data/gems/server/Rakefile +0 -4
  109. data/gems/server/exe/itsi +13 -2
  110. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
  111. data/gems/server/lib/itsi/http_request.rb +40 -9
  112. data/gems/server/lib/itsi/http_response.rb +2 -1
  113. data/gems/server/lib/itsi/passfile.rb +0 -1
  114. data/gems/server/lib/itsi/server/config/config_helpers.rb +20 -8
  115. data/gems/server/lib/itsi/server/config/dsl.rb +20 -435
  116. data/gems/server/lib/itsi/server/config/known_paths.rb +4 -1
  117. data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
  118. data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
  119. data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
  120. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
  121. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
  122. data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
  123. data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
  124. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
  125. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
  126. data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
  127. data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
  128. data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
  129. data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
  130. data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
  131. data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
  132. data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
  133. data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
  134. data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
  135. data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
  136. data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
  137. data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
  138. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
  139. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
  140. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
  141. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
  142. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
  143. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
  144. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
  145. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
  146. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
  147. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
  148. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
  149. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
  150. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
  151. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
  152. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
  153. data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
  154. data/gems/server/lib/itsi/server/config/middleware/error_response.md +61 -0
  155. data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
  156. data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
  157. data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
  158. data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
  159. data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
  160. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
  161. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
  162. data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
  163. data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
  164. data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
  165. data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
  166. data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
  167. data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
  168. data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
  169. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
  170. data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
  171. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
  172. data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
  173. data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
  174. data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
  175. data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
  176. data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
  177. data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
  178. data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
  179. data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
  180. data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
  181. data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
  182. data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
  183. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +73 -0
  184. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
  185. data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
  186. data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
  187. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
  188. data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
  189. data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
  190. data/gems/server/lib/itsi/server/config/option.rb +5 -0
  191. data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
  192. data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
  193. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
  194. data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
  195. data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
  196. data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
  197. data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
  198. data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
  199. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
  200. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
  201. data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
  202. data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
  203. data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
  204. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
  205. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
  206. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
  207. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
  208. data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
  209. data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
  210. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
  211. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
  212. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
  213. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
  214. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
  215. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
  216. data/gems/server/lib/itsi/server/config/options/include.md +20 -0
  217. data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
  218. data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
  219. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
  220. data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
  221. data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
  222. data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
  223. data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
  224. data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
  225. data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
  226. data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
  227. data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
  228. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
  229. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
  230. data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
  231. data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
  232. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
  233. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
  234. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
  235. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
  236. data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
  237. data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
  238. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
  239. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
  240. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
  241. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
  242. data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
  243. data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
  244. data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
  245. data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
  246. data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
  247. data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
  248. data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
  249. data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
  250. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
  251. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
  252. data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
  253. data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
  254. data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
  255. data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
  256. data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
  257. data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
  258. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
  259. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
  260. data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
  261. data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
  262. data/gems/server/lib/itsi/server/config/typed_struct.rb +59 -20
  263. data/gems/server/lib/itsi/server/config.rb +77 -48
  264. data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
  265. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +1 -1
  266. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +11 -4
  267. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -3
  268. data/gems/server/lib/itsi/server/route_tester.rb +58 -8
  269. data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
  270. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
  271. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +5 -4
  272. data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
  273. data/gems/server/lib/itsi/server/version.rb +1 -1
  274. data/gems/server/lib/itsi/server.rb +98 -14
  275. data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
  276. data/gems/server/test/helpers/test_helper.rb +89 -29
  277. data/gems/server/test/middleware/allow_list.rb +128 -0
  278. data/gems/server/test/middleware/auth_api_key.rb +141 -0
  279. data/gems/server/test/middleware/auth_basic.rb +91 -0
  280. data/gems/server/test/middleware/auth_jwt.rb +214 -0
  281. data/gems/server/test/middleware/cache_control.rb +82 -0
  282. data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
  283. data/gems/server/test/middleware/compression.rb +89 -0
  284. data/gems/server/test/middleware/cors.rb +113 -0
  285. data/gems/server/test/middleware/csp.rb +62 -0
  286. data/gems/server/test/middleware/deny_list.rb +131 -0
  287. data/gems/server/test/middleware/endpoint.rb +300 -0
  288. data/gems/server/test/middleware/etag.rb +75 -0
  289. data/gems/server/test/middleware/grpc/grpc.rb +158 -0
  290. data/gems/server/test/middleware/grpc/test_service.proto +32 -0
  291. data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
  292. data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
  293. data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
  294. data/gems/server/test/middleware/header_interpolation.rb +35 -0
  295. data/gems/server/test/middleware/intrusion_protection.rb +259 -0
  296. data/gems/server/test/middleware/location.rb +220 -0
  297. data/gems/server/test/middleware/max_body.rb +20 -0
  298. data/gems/server/test/middleware/proxy.rb +415 -0
  299. data/gems/server/test/middleware/rate_limit.rb +211 -0
  300. data/gems/server/test/middleware/redirect.rb +85 -0
  301. data/gems/server/test/middleware/request_headers.rb +50 -0
  302. data/gems/server/test/middleware/response_headers.rb +50 -0
  303. data/gems/server/test/middleware/static_assets.rb +374 -0
  304. data/gems/server/test/middleware/static_response.rb +56 -0
  305. data/gems/server/test/middleware/string_rewrite.rb +112 -0
  306. data/gems/server/test/options/bind.rb +47 -0
  307. data/gems/server/test/options/header_read_timeout.rb +23 -0
  308. data/gems/server/test/options/test_request_timeout.rb +16 -0
  309. data/gems/server/test/options/test_workers.rb +2 -4
  310. data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
  311. data/grpc_test/Itsi.rb +11 -0
  312. data/grpc_test/echo.proto +14 -0
  313. data/grpc_test/echo_pb.rb +16 -0
  314. data/grpc_test/echo_service_impl.rb +8 -0
  315. data/grpc_test/echo_services_pb.rb +22 -0
  316. data/lib/itsi/version.rb +1 -1
  317. data/tasks.txt +15 -72
  318. metadata +210 -7
  319. data/gems/server/lib/itsi/server/default_config/Itsi-rackup.rb +0 -119
@@ -0,0 +1,415 @@
1
+ require_relative "../helpers/test_helper"
2
+
3
+ class TestProxy < Minitest::Test
4
+
5
+ def test_successful_forwarding
6
+
7
+ backend_bind = free_bind
8
+ server(
9
+ itsi_rb: lambda do
10
+ log_requests before: { format: "GET {path_and_query}", level: "INFO"}
11
+ get("/foo") { |r|
12
+ r.ok "backend success. #{r.query_params["bar"]}"
13
+ }
14
+ end,
15
+ bind: backend_bind
16
+ ) do
17
+ server(
18
+ itsi_rb: lambda do
19
+ proxy \
20
+ to: "#{backend_bind}{path_and_query}",
21
+ backends: ["#{backend_bind}"],
22
+ backend_priority: "round_robin",
23
+ headers: {},
24
+ verify_ssl: false,
25
+ timeout: 30,
26
+ tls_sni: false,
27
+ error_response: "internal_server_error"
28
+ get("/foo") { |r|
29
+ r.ok "should not get here"
30
+ }
31
+ end
32
+ ) do
33
+ res = get_resp("/foo?bar=baz")
34
+ # Expect that the proxy forwards the request to the backend.
35
+ assert_equal "200", res.code, "Expected success status from backend"
36
+ assert_equal "backend success. baz", res.body
37
+ end
38
+ end
39
+ end
40
+
41
+ # Test that an invalid target URL (i.e. an unparseable URL) yields an error response.
42
+ def test_invalid_target_url_returns_error
43
+ server(
44
+ itsi_rb: lambda do
45
+ proxy \
46
+ to: "not_a_valid_url",
47
+ backends: ["127.0.0.1:3001"],
48
+ backend_priority: "round_robin",
49
+ headers: {},
50
+ verify_ssl: false,
51
+ timeout: 30,
52
+ tls_sni: false,
53
+ error_response: "bad_gateway"
54
+ get("/foo") { |r| r.ok "should not get here" }
55
+ end
56
+ ) do
57
+ res = get_resp("/foo")
58
+ assert_equal "502", res.code, "Expected error response code 502 for invalid URL"
59
+ end
60
+ end
61
+
62
+ def test_overriding_headers
63
+
64
+ backend_bind = free_bind
65
+ server(
66
+ itsi_rb: lambda do
67
+ log_requests before: { format: "GET {path_and_query}", level: "INFO"}
68
+ get("/header-test") do |r|
69
+ # Return the incoming header value.
70
+ r.ok r.header("X-Forwarded-For").first
71
+ end
72
+ end,
73
+ bind: backend_bind
74
+ ) do
75
+
76
+ # Start proxy server.
77
+ server(
78
+ itsi_rb: lambda do
79
+ proxy \
80
+ to: "#{backend_bind}{path}{query}",
81
+ backends: ["#{backend_bind}"],
82
+ backend_priority: "round_robin",
83
+ headers: { "X-Forwarded-For" => "{addr}" },
84
+ verify_ssl: false,
85
+ timeout: 30,
86
+ tls_sni: false,
87
+ error_response: "internal_server_error"
88
+ get("/header-test") { |r| r.ok "should not get here" }
89
+ end
90
+ ) do
91
+ res = get_resp("/header-test")
92
+ # Expect that the overriding header "X-Forwarded-For" is set to the client’s address.
93
+ assert_equal "127.0.0.1", res.body, "Expected the header to be set to the client address"
94
+ end
95
+ end
96
+ end
97
+
98
+ def test_proxy_with_static_to_only
99
+ backend_bind = free_bind
100
+ server(
101
+ itsi_rb: lambda do
102
+ get("/static") { |r| r.ok "static response" }
103
+ end,
104
+ bind: backend_bind
105
+ ) do
106
+ server(
107
+ itsi_rb: lambda do
108
+ proxy \
109
+ to: "#{backend_bind}{path}{query}",
110
+ backend_priority: "round_robin",
111
+ headers: {},
112
+ verify_ssl: false,
113
+ timeout: 30,
114
+ tls_sni: false,
115
+ error_response: "internal_server_error"
116
+ get("/static") { |r| r.ok "should not get here" }
117
+ end
118
+ ) do
119
+ res = get_resp("/static")
120
+ assert_equal "200", res.code, "Expected success status from static 'to' URL"
121
+ assert_equal "static response", res.body
122
+ end
123
+ end
124
+ end
125
+
126
+ def test_proxy_with_backend_host_override
127
+ backend_bind1 = free_bind
128
+ backend_bind2 = free_bind
129
+ # Start two dummy backends that respond with their Host header.
130
+ thread1 = Thread.new do
131
+ server(
132
+ itsi_rb: lambda do
133
+ get("/host-test") { |r| r.ok r.header("Host").first }
134
+ end,
135
+ bind: backend_bind1
136
+ ){ sleep 1 }
137
+ end
138
+ thread2 = Thread.new do
139
+ server(
140
+ itsi_rb: lambda do
141
+ get("/host-test") { |r| r.ok r.header("Host").first }
142
+ end,
143
+ bind: backend_bind2
144
+ ){ sleep 1 }
145
+ end
146
+
147
+ sleep 0.1
148
+
149
+ server(
150
+ itsi_rb: lambda do
151
+ proxy \
152
+ to: "#{backend_bind1}{path}{query}",
153
+ backends: ["#{backend_bind1}", "#{backend_bind2}"],
154
+ backend_priority: "round_robin",
155
+ headers: { "Host" => "custom.backend.example.com" },
156
+ verify_ssl: false,
157
+ timeout: 30,
158
+ tls_sni: true,
159
+ error_response: "internal_server_error"
160
+ get("/host-test") { |r| r.ok "should not get here" }
161
+ end
162
+ ) do
163
+ res = get_resp("/host-test")
164
+ assert_equal "200", res.code, "Expected successful response with host override"
165
+ assert_equal "custom.backend.example.com", res.body, "Expected the Host header to be overridden"
166
+ end
167
+
168
+ thread1.kill
169
+ thread2.kill
170
+ end
171
+
172
+ def test_proxy_timeout
173
+ backend_bind = free_bind
174
+ # Start a backend server that sleeps for 1 second before responding.
175
+ server(
176
+ itsi_rb: lambda do
177
+ get("/slow") do |r|
178
+ sleep 1
179
+ r.ok "delayed response"
180
+ end
181
+ end,
182
+ bind: backend_bind
183
+ ) do
184
+ server(
185
+ itsi_rb: lambda do
186
+ # Set a very short timeout (0 seconds) to force a timeout.
187
+ proxy \
188
+ to: "#{backend_bind}{path}{query}",
189
+ backends: ["#{backend_bind}"],
190
+ backend_priority: "round_robin",
191
+ headers: {},
192
+ verify_ssl: false,
193
+ timeout: 0,
194
+ tls_sni: false,
195
+ error_response: "gateway_timeout"
196
+ get("/slow") { |r| r.ok "should not get here" }
197
+ end
198
+ ) do
199
+ res = get_resp("/slow")
200
+ assert_equal "504", res.code, "Expected 504 Gateway Timeout when backend response exceeds timeout"
201
+ end
202
+ end
203
+ end
204
+
205
+ def test_error_response_internal_server_error
206
+ server(
207
+ itsi_rb: lambda do
208
+ proxy \
209
+ to: "invalid_url",
210
+ backends: ["127.0.0.1:3001"],
211
+ backend_priority: "round_robin",
212
+ headers: {},
213
+ verify_ssl: false,
214
+ timeout: 30,
215
+ tls_sni: false,
216
+ error_response: "internal_server_error"
217
+ get("/foo") { |r| r.ok "should not get here" }
218
+ end
219
+ ) do
220
+ res = get_resp("/foo")
221
+ assert_equal "500", res.code, "Expected error response code 500 for internal_server_error"
222
+ end
223
+ end
224
+
225
+ def test_error_response_not_found
226
+ server(
227
+ itsi_rb: lambda do
228
+ proxy \
229
+ to: "invalid_url",
230
+ backends: ["127.0.0.1:3001"],
231
+ backend_priority: "round_robin",
232
+ headers: {},
233
+ verify_ssl: false,
234
+ timeout: 30,
235
+ tls_sni: false,
236
+ error_response: "not_found"
237
+ get("/foo") { |r| r.ok "should not get here" }
238
+ end
239
+ ) do
240
+ res = get_resp("/foo")
241
+ assert_equal "404", res.code, "Expected error response code 404 for not_found"
242
+ end
243
+ end
244
+
245
+ def test_error_response_unauthorized
246
+ server(
247
+ itsi_rb: lambda do
248
+ proxy \
249
+ to: "invalid_url",
250
+ backends: ["127.0.0.1:3001"],
251
+ backend_priority: "round_robin",
252
+ headers: {},
253
+ verify_ssl: false,
254
+ timeout: 30,
255
+ tls_sni: false,
256
+ error_response: "unauthorized"
257
+ get("/foo") { |r| r.ok "should not get here" }
258
+ end
259
+ ) do
260
+ res = get_resp("/foo")
261
+ assert_equal "401", res.code, "Expected error response code 401 for unauthorized"
262
+ end
263
+ end
264
+
265
+ def test_error_response_forbidden
266
+ server(
267
+ itsi_rb: lambda do
268
+ proxy \
269
+ to: "invalid_url",
270
+ backends: ["127.0.0.1:3001"],
271
+ backend_priority: "round_robin",
272
+ headers: {},
273
+ verify_ssl: false,
274
+ timeout: 30,
275
+ tls_sni: false,
276
+ error_response: "forbidden"
277
+ get("/foo") { |r| r.ok "should not get here" }
278
+ end
279
+ ) do
280
+ res = get_resp("/foo")
281
+ assert_equal "403", res.code, "Expected error response code 403 for forbidden"
282
+ end
283
+ end
284
+
285
+ def test_error_response_payload_too_large
286
+ server(
287
+ itsi_rb: lambda do
288
+ proxy \
289
+ to: "invalid_url",
290
+ backends: ["127.0.0.1:3001"],
291
+ backend_priority: "round_robin",
292
+ headers: {},
293
+ verify_ssl: false,
294
+ timeout: 30,
295
+ tls_sni: false,
296
+ error_response: "payload_too_large"
297
+ get("/foo") { |r| r.ok "should not get here" }
298
+ end
299
+ ) do
300
+ res = get_resp("/foo")
301
+ assert_equal "413", res.code, "Expected error response code 413 for payload_too_large"
302
+ end
303
+ end
304
+
305
+ def test_error_response_too_many_requests
306
+ server(
307
+ itsi_rb: lambda do
308
+ proxy \
309
+ to: "invalid_url",
310
+ backends: ["127.0.0.1:3001"],
311
+ backend_priority: "round_robin",
312
+ headers: {},
313
+ verify_ssl: false,
314
+ timeout: 30,
315
+ tls_sni: false,
316
+ error_response: "too_many_requests"
317
+ get("/foo") { |r| r.ok "should not get here" }
318
+ end
319
+ ) do
320
+ res = get_resp("/foo")
321
+ assert_equal "429", res.code, "Expected error response code 429 for too_many_requests"
322
+ end
323
+ end
324
+
325
+ def test_error_response_service_unavailable
326
+ server(
327
+ itsi_rb: lambda do
328
+ proxy \
329
+ to: "invalid_url",
330
+ backends: ["127.0.0.1:3001"],
331
+ backend_priority: "round_robin",
332
+ headers: {},
333
+ verify_ssl: false,
334
+ timeout: 30,
335
+ tls_sni: false,
336
+ error_response: "service_unavailable"
337
+ get("/foo") { |r| r.ok "should not get here" }
338
+ end
339
+ ) do
340
+ res = get_resp("/foo")
341
+ assert_equal "503", res.code, "Expected error response code 503 for service_unavailable"
342
+ end
343
+ end
344
+
345
+ def test_error_response_gateway_timeout
346
+ server(
347
+ itsi_rb: lambda do
348
+ proxy \
349
+ to: "invalid_url",
350
+ backends: ["127.0.0.1:3001"],
351
+ backend_priority: "round_robin",
352
+ headers: {},
353
+ verify_ssl: false,
354
+ timeout: 30,
355
+ tls_sni: false,
356
+ error_response: "gateway_timeout"
357
+ get("/foo") { |r| r.ok "should not get here" }
358
+ end
359
+ ) do
360
+ res = get_resp("/foo")
361
+ assert_equal "504", res.code, "Expected error response code 504 for gateway_timeout"
362
+ end
363
+ end
364
+
365
+ def test_failover_behavior
366
+ # Obtain two free bind addresses for backend servers.
367
+ backend1_bind = free_bind
368
+ backend2_bind = free_bind
369
+
370
+ backend2_server = Itsi::Server.start_in_background_thread(binds: [backend2_bind]) do
371
+ get("/failover") { |r| r.ok "backend2" }
372
+ end
373
+
374
+ # backend2_server.stop
375
+
376
+ # # Allow the backend servers to start.
377
+ sleep 0.2
378
+
379
+
380
+ # Start the proxy server with an ordered backend selection.
381
+ server(
382
+ cleanup: false,
383
+ itsi_rb: lambda do
384
+ proxy \
385
+ to: "http://proxied_host.com{path}{query}",
386
+ backends: [backend1_bind[/\/\/(.*)/,1], backend2_bind[/\/\/(.*)/,1]],
387
+ backend_priority: "ordered",
388
+ headers: {},
389
+ verify_ssl: false,
390
+ timeout: 30,
391
+ tls_sni: false,
392
+ error_response: "internal_server_error"
393
+ get("/failover") { |r| r.ok "should not get here" }
394
+ end
395
+ ) do
396
+
397
+
398
+ res = get_resp("/failover")
399
+ assert_equal "200", res.code, "Expected success when fallback backend is available"
400
+ assert_equal "backend2", res.body, "Expected response from backend2"
401
+
402
+ backend1_server = Itsi::Server.start_in_background_thread(binds: [backend1_bind]) do
403
+ get("/failover") { |r| r.ok "backend1" }
404
+ end
405
+
406
+ sleep 1
407
+
408
+ res = get_resp("/failover")
409
+ assert_equal "200", res.code, "Expected reversion when primary becomes available"
410
+ assert_equal "backend1", res.body, "Expected response from backend1 with ordered priority"
411
+ end
412
+
413
+ Itsi::Server.stop_background_threads
414
+ end
415
+ end
@@ -0,0 +1,211 @@
1
+ require_relative "../helpers/test_helper"
2
+ require "redis"
3
+
4
+ class TestRateLimit < Minitest::Test
5
+
6
+ # 1. In‑memory: allow up to N requests, then 429
7
+ def test_in_memory_limit
8
+ server(
9
+ itsi_rb: lambda do
10
+ rate_limit requests: 3, seconds: 2
11
+ get("/foo") { |r| r.ok "ok" }
12
+ end
13
+ ) do
14
+ 3.times do
15
+ res = get_resp("/foo")
16
+ assert_equal "200", res.code
17
+ assert_equal "ok", res.body
18
+ end
19
+ # Next one should be rate‑limited
20
+ res = get_resp("/foo")
21
+ assert_equal "429", res.code
22
+ # default error_response body is the standard message
23
+ assert_match /Slow down!/, res.body
24
+ assert_match /429/, res.body
25
+
26
+ assert_equal 3.to_s, res["X-RateLimit-Limit"]
27
+ assert_equal "0", res["X-RateLimit-Remaining"]
28
+ assert_match /\d+/, res["X-RateLimit-Reset"]
29
+ assert_equal res["X-RateLimit-Reset"], res["Retry-After"]
30
+ end
31
+ end
32
+
33
+ # 2. Time window resets after `seconds`
34
+ def test_window_reset
35
+ server(
36
+ itsi_rb: lambda do
37
+ rate_limit requests: 1, seconds: 1
38
+ get("/bar") { |r| r.ok "bar" }
39
+ end
40
+ ) do
41
+ res1 = get_resp("/bar")
42
+ assert_equal "200", res1.code
43
+
44
+ res2 = get_resp("/bar")
45
+ assert_equal "429", res2.code
46
+
47
+ sleep 1.1
48
+ res3 = get_resp("/bar")
49
+ assert_equal "200", res3.code
50
+ end
51
+ end
52
+
53
+ # 3. Key by header
54
+ def test_key_by_header
55
+ server(
56
+ itsi_rb: lambda do
57
+ rate_limit \
58
+ requests: 1,
59
+ seconds: 60,
60
+ key: { parameter: { header: { name: "X-Client-Id" } } }
61
+ get("/h") { |r| r.ok r.header("X-Client-Id").first }
62
+ end
63
+ ) do
64
+ h1 = { "X-Client-Id" => "A" }
65
+ h2 = { "X-Client-Id" => "B" }
66
+
67
+ # A once OK, then limited
68
+ res1 = get_resp("/h", h1)
69
+ assert_equal "200", res1.code
70
+ assert_equal "A", res1.body
71
+
72
+ res2 = get_resp("/h", h1)
73
+ assert_equal "429", res2.code
74
+
75
+ # B independent count
76
+ res3 = get_resp("/h", h2)
77
+ assert_equal "200", res3.code
78
+ assert_equal "B", res3.body
79
+ end
80
+ end
81
+
82
+ # 4. Key by query
83
+ def test_key_by_query
84
+ server(
85
+ itsi_rb: lambda do
86
+ rate_limit \
87
+ requests: 1,
88
+ seconds: 60,
89
+ key: { parameter: { query: "user" } }
90
+ get("/q") { |r| r.ok r.query_params["user"] }
91
+ end
92
+ ) do
93
+ res1 = get_resp("/q?user=foo")
94
+ assert_equal "200", res1.code
95
+ assert_equal "foo", res1.body
96
+
97
+ res2 = get_resp("/q?user=foo")
98
+ assert_equal "429", res2.code
99
+ end
100
+ end
101
+
102
+ # 5. Custom error_response
103
+ def test_custom_error_response
104
+ server(
105
+ itsi_rb: lambda do
106
+ rate_limit \
107
+ requests: 1,
108
+ seconds: 60,
109
+ error_response: {
110
+ code: 429,
111
+ plaintext: { inline: "Slow down" },
112
+ default: "plaintext"
113
+ }
114
+ get("/") { |r| r.ok "never" }
115
+ end
116
+ ) do
117
+ res = 5.times.map{ get_resp("/") }.last
118
+ assert_equal "429", res.code
119
+ assert_equal "Slow down", res.body
120
+ end
121
+ end
122
+
123
+ # 6. Skip Redis tests if Redis not available
124
+ def test_redis_store_unavailable_skips
125
+ skip "Redis not running" unless begin
126
+ Redis.new(url: ENV.fetch("REDIS_URL","redis://localhost:6379/15")).ping == "PONG"
127
+ rescue
128
+ false
129
+ end
130
+ end
131
+
132
+ # 7. Redis‑backed limiting
133
+ def test_redis_backed_limit
134
+ redis_url = ENV.fetch("REDIS_URL","redis://localhost:6379/15")
135
+ ENV["REDIS_URL"] = redis_url
136
+ server(
137
+ itsi_rb: lambda do
138
+ rate_limit \
139
+ requests: 2,
140
+ seconds: 60,
141
+ store_config: { redis: { connection_url: ENV["REDIS_URL"] } }
142
+ get("/r") { |r| r.ok "ok" }
143
+ end
144
+ ) do
145
+ client = Redis.new(url: redis_url)
146
+ client.flushdb
147
+
148
+ 2.times do
149
+ res = get_resp("/r")
150
+ assert_equal "200", res.code
151
+ assert_equal "ok", res.body
152
+ end
153
+
154
+ # Third hit is rate‑limited
155
+ res3 = get_resp("/r")
156
+ assert_equal "429", res3.code
157
+
158
+ client.flushdb
159
+ end
160
+ end
161
+
162
+ # 8. Trusted proxy: forwarded IP is used for rate limit key
163
+ def test_trusted_proxy_respects_forwarded_ip
164
+ server(
165
+ itsi_rb: lambda do
166
+ rate_limit \
167
+ requests: 1,
168
+ seconds: 60,
169
+ trusted_proxies: {
170
+ "127.0.0.1" => { header: { name: "X-Forwarded-For" } }
171
+ }
172
+ get("/ip") { |r| r.ok "ok" }
173
+ end
174
+ ) do
175
+ # First IP: allowed
176
+ res1 = get_resp("/ip", { "X-Forwarded-For" => "198.51.100.1" })
177
+ assert_equal "200", res1.code
178
+
179
+ # Second hit from same client IP: blocked
180
+ res2 = get_resp("/ip", { "X-Forwarded-For" => "198.51.100.1" })
181
+ assert_equal "429", res2.code
182
+
183
+ # Third hit from *different* IP: allowed
184
+ res3 = get_resp("/ip", { "X-Forwarded-For" => "198.51.100.2" })
185
+ assert_equal "200", res3.code
186
+ end
187
+ end
188
+
189
+ # 9. Untrusted proxy: forwarded header is ignored
190
+ def test_untrusted_proxy_ignores_forwarded_ip
191
+ server(
192
+ itsi_rb: lambda do
193
+ rate_limit \
194
+ requests: 1,
195
+ seconds: 60,
196
+ trusted_proxies: {
197
+ "10.0.0.1" => { header: { name: "X-Forwarded-For" } }
198
+ }
199
+ get("/untrusted") { |r| r.ok "ok" }
200
+ end
201
+ ) do
202
+ # Even though header says different IP, it’s ignored (127.0.0.1 is used)
203
+ res1 = get_resp("/untrusted", { "X-Forwarded-For" => "198.51.100.1" })
204
+ assert_equal "200", res1.code
205
+
206
+ # Still treated as same client (127.0.0.1), so next request is blocked
207
+ res2 = get_resp("/untrusted", { "X-Forwarded-For" => "198.51.100.2" })
208
+ assert_equal "429", res2.code
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,85 @@
1
+ require_relative "../helpers/test_helper"
2
+
3
+ class TestRedirect < Minitest::Test
4
+ def test_permanent_redirect
5
+ server(
6
+ itsi_rb: lambda do
7
+ redirect to: "https://example.com/new", type: "moved_permanently"
8
+ get("/foo") { |r| r.ok "should not get here" }
9
+ end
10
+ ) do
11
+ res = get_resp("/foo")
12
+ assert_equal "301", res.code, "Expected status 301 for permanent redirect"
13
+ assert_equal "https://example.com/new", res["Location"]
14
+ end
15
+ end
16
+
17
+ def test_temporary_redirect
18
+ server(
19
+ itsi_rb: lambda do
20
+ redirect to: "https://example.com/new", type: "temporary"
21
+ get("/foo") { |r| r.ok "should not get here" }
22
+ end
23
+ ) do
24
+ res = get_resp("/foo")
25
+ assert_equal "307", res.code, "Expected status 307 for temporary redirect"
26
+ assert_equal "https://example.com/new", res["Location"]
27
+ end
28
+ end
29
+
30
+ def test_found_redirect
31
+ server(
32
+ itsi_rb: lambda do
33
+ redirect to: "https://example.com/new", type: "found"
34
+ get("/foo") { |r| r.ok "should not get here" }
35
+ end
36
+ ) do
37
+ res = get_resp("/foo")
38
+ assert_equal "302", res.code, "Expected status 302 for found redirect"
39
+ assert_equal "https://example.com/new", res["Location"]
40
+ end
41
+ end
42
+
43
+ def test_moved_permanently_redirect
44
+ server(
45
+ itsi_rb: lambda do
46
+ redirect to: "https://example.com/new", type: "permanent"
47
+ get("/foo") { |r| r.ok "should not get here" }
48
+ end
49
+ ) do
50
+ res = get_resp("/foo")
51
+ assert_equal "308", res.code, "Expected status 308 for moved permanently redirect"
52
+ assert_equal "https://example.com/new", res["Location"]
53
+ end
54
+ end
55
+
56
+ def test_relative_redirect
57
+ server(
58
+ itsi_rb: lambda do
59
+ # Use a relative URL as the target
60
+ redirect to: "/new/path", type: "permanent"
61
+ get("/foo") { |r| r.ok "should not get here" }
62
+ end
63
+ ) do
64
+ res = get_resp("/foo")
65
+ assert_equal "308", res.code, "Expected status 308 for permanent redirect"
66
+ # For a relative URL, the Location header should match exactly
67
+ assert_equal "/new/path", res["Location"]
68
+ end
69
+ end
70
+
71
+ def test_relative_redirect_with_placeholder
72
+ server(
73
+ itsi_rb: lambda do
74
+ # Use a template that interpolates the request path into a relative URL.
75
+ # For a request to "/foo", the resulting Location should be "/new/foo".
76
+ redirect to: "/new{path}", type: "temporary"
77
+ get("/foo") { |r| r.ok "should not get here" }
78
+ end
79
+ ) do
80
+ res = get_resp("/foo")
81
+ assert_equal "307", res.code, "Expected status 307 for temporary redirect"
82
+ assert_equal "/new/foo", res["Location"]
83
+ end
84
+ end
85
+ end