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
@@ -2,9 +2,7 @@ use crate::ruby_types::itsi_server::itsi_server_config::ItsiServerConfig;
2
2
  use crate::server::signal::SIGNAL_HANDLER_CHANNEL;
3
3
  use crate::server::{lifecycle_event::LifecycleEvent, process_worker::ProcessWorker};
4
4
  use itsi_error::{ItsiError, Result};
5
- use itsi_rb_helpers::{
6
- call_proc_and_log_errors, call_with_gvl, call_without_gvl, create_ruby_thread,
7
- };
5
+ use itsi_rb_helpers::{call_with_gvl, call_without_gvl, create_ruby_thread};
8
6
  use itsi_tracing::{error, info, warn};
9
7
  use magnus::Value;
10
8
  use nix::{libc::exit, unistd::Pid};
@@ -56,6 +54,12 @@ impl ClusterMode {
56
54
  .expect("Failed to build Tokio runtime")
57
55
  }
58
56
 
57
+ pub fn invoke_hook(&self, hook_name: &str) {
58
+ if let Some(hook) = self.server_config.server_params.read().hooks.get(hook_name) {
59
+ call_with_gvl(|_| hook.call::<_, Value>(()).ok());
60
+ }
61
+ }
62
+
59
63
  #[allow(clippy::await_holding_lock)]
60
64
  pub async fn handle_lifecycle_event(
61
65
  self: Arc<Self>,
@@ -70,10 +74,12 @@ impl ClusterMode {
70
74
  LifecycleEvent::Shutdown => {
71
75
  self.server_config.stop_watcher()?;
72
76
  self.shutdown().await?;
77
+ self.invoke_hook("before_shutdown");
73
78
  Ok(())
74
79
  }
75
80
  LifecycleEvent::Restart => {
76
- if self.server_config.check_config() {
81
+ if self.server_config.check_config().await {
82
+ self.invoke_hook("before_restart");
77
83
  self.server_config.dup_fds()?;
78
84
  self.shutdown().await.ok();
79
85
  info!("Shutdown complete. Calling reload exec");
@@ -82,7 +88,7 @@ impl ClusterMode {
82
88
  Ok(())
83
89
  }
84
90
  LifecycleEvent::Reload => {
85
- if !self.server_config.check_config() {
91
+ if !self.server_config.check_config().await {
86
92
  return Ok(());
87
93
  }
88
94
  let should_reexec = self.server_config.clone().reload(true)?;
@@ -268,15 +274,7 @@ impl ClusterMode {
268
274
  #[instrument(skip(self), fields(mode = "cluster", pid=format!("{:?}", Pid::this())))]
269
275
  pub fn run(self: Arc<Self>) -> Result<()> {
270
276
  info!("Starting in Cluster mode");
271
- if let Some(proc) = self
272
- .server_config
273
- .server_params
274
- .read()
275
- .hooks
276
- .get("before_fork")
277
- {
278
- call_with_gvl(|_| call_proc_and_log_errors(proc.clone()))
279
- }
277
+ self.invoke_hook("before_fork");
280
278
  self.process_workers
281
279
  .lock()
282
280
  .iter()
@@ -290,7 +288,15 @@ impl ClusterMode {
290
288
 
291
289
  self.build_runtime().block_on(async {
292
290
  let self_ref = self_ref.clone();
293
- let mut memory_check_interval = time::interval(time::Duration::from_secs(2));
291
+ let memory_check_duration = if self_ref.server_config.server_params.read().worker_memory_limit.is_some(){
292
+ time::Duration::from_secs(15)
293
+ } else {
294
+ time::Duration::from_secs(60 * 60 * 24 * 365 * 100)
295
+ };
296
+
297
+ let mut memory_check_interval = time::interval(memory_check_duration);
298
+
299
+ self.invoke_hook("after_start");
294
300
 
295
301
  loop {
296
302
  tokio::select! {
@@ -314,7 +320,7 @@ impl ClusterMode {
314
320
  if let Some(current_mem_usage) = largest_worker.memory_usage(){
315
321
  if current_mem_usage > memory_limit {
316
322
  largest_worker.reboot(self_ref.clone()).await.ok();
317
- if let Some(hook) = self_ref.server_config.server_params.read().hooks.get("after_memory_threshold_reached") {
323
+ if let Some(hook) = self_ref.server_config.server_params.read().hooks.get("after_memory_limit_reached") {
318
324
  call_with_gvl(|_| hook.call::<_, Value>((largest_worker.pid(),)).ok() );
319
325
  }
320
326
  }
@@ -19,7 +19,7 @@ use itsi_rb_helpers::{
19
19
  call_with_gvl, call_without_gvl, create_ruby_thread, funcall_no_ret, print_rb_backtrace,
20
20
  };
21
21
  use itsi_tracing::{debug, error, info};
22
- use magnus::value::ReprValue;
22
+ use magnus::{value::ReprValue, Value};
23
23
  use nix::unistd::Pid;
24
24
  use parking_lot::RwLock;
25
25
  use std::{
@@ -83,8 +83,7 @@ impl SingleMode {
83
83
  builder
84
84
  .thread_name("itsi-server-accept-loop")
85
85
  .thread_stack_size(3 * 1024 * 1024)
86
- .enable_io()
87
- .enable_time()
86
+ .enable_all()
88
87
  .build()
89
88
  .expect("Failed to build Tokio runtime")
90
89
  }
@@ -160,12 +159,12 @@ impl SingleMode {
160
159
  pub fn start_monitors(
161
160
  self: Arc<Self>,
162
161
  thread_workers: Arc<Vec<Arc<ThreadWorker>>>,
163
- ) -> magnus::Thread {
162
+ ) -> Option<magnus::Thread> {
164
163
  call_with_gvl(move |_| {
165
164
  create_ruby_thread(move || {
166
165
  call_without_gvl(move || {
167
166
  let monitor_runtime = RuntimeBuilder::new_current_thread()
168
- .enable_time()
167
+ .enable_all()
169
168
  .build()
170
169
  .unwrap();
171
170
  let receiver = self.clone();
@@ -190,10 +189,10 @@ impl SingleMode {
190
189
  lifecycle_event = lifecycle_rx.recv() => {
191
190
  match lifecycle_event {
192
191
  Ok(LifecycleEvent::Restart) => {
193
- receiver.restart().ok();
192
+ receiver.restart().await.ok();
194
193
  }
195
194
  Ok(LifecycleEvent::Reload) => {
196
- receiver.reload().ok();
195
+ receiver.reload().await.ok();
197
196
  }
198
197
  Ok(LifecycleEvent::Shutdown) => {
199
198
  break;
@@ -233,6 +232,11 @@ impl SingleMode {
233
232
 
234
233
  let (shutdown_sender, _) = watch::channel(RunningPhase::Running);
235
234
  let monitor_thread = self.clone().start_monitors(thread_workers.clone());
235
+ if monitor_thread.is_none() {
236
+ error!("Failed to start monitor thread");
237
+ return Err(ItsiError::new("Failed to start monitor thread"));
238
+ }
239
+ let monitor_thread = monitor_thread.unwrap();
236
240
  if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
237
241
  return Ok(());
238
242
  }
@@ -290,6 +294,7 @@ impl SingleMode {
290
294
  }
291
295
  lifecycle_event = lifecycle_rx.recv() => match lifecycle_event{
292
296
  Ok(LifecycleEvent::Shutdown) => {
297
+ debug!("Received lifecycle event: {:?}", lifecycle_event);
293
298
  shutdown_sender.send(RunningPhase::ShutdownPending).unwrap();
294
299
  tokio::time::sleep(Duration::from_millis(25)).await;
295
300
  for _i in 0..workers_clone.len() {
@@ -303,11 +308,23 @@ impl SingleMode {
303
308
  }
304
309
  }
305
310
  }
306
- while let Some(_res) = acceptor_task_set.join_next().await {}
311
+
312
+ let deadline = Instant::now()
313
+ + Duration::from_secs_f64(self_ref.server_config.server_params.read().shutdown_timeout);
314
+ tokio::select! {
315
+ _ = async {
316
+ while let Some(_res) = acceptor_task_set.join_next().await {}
317
+ } => {},
318
+ _ = tokio::time::sleep_until(tokio::time::Instant::from_std(deadline)) => {},
319
+ }
307
320
  });
308
321
 
309
322
  }
310
323
 
324
+ if self.is_single_mode() {
325
+ self.invoke_hook("after_start");
326
+ }
327
+
311
328
  while let Some(_res) = listener_task_set.join_next().await {}
312
329
 
313
330
  // Explicitly drop all listeners to ensure file descriptors are released
@@ -346,15 +363,24 @@ impl SingleMode {
346
363
  sleep(Duration::from_millis(50));
347
364
  }
348
365
 
366
+ if self.is_single_mode() {
367
+ self.invoke_hook("before_shutdown");
368
+ }
369
+
349
370
  if self.restart_requested.load(Ordering::SeqCst) {
350
371
  self.restart_requested.store(false, Ordering::SeqCst);
351
372
  info!("Worker restarting");
352
373
  self.run()?;
353
374
  }
375
+
354
376
  debug!("Runtime has shut down");
355
377
  result
356
378
  }
357
379
 
380
+ pub fn is_single_mode(&self) -> bool {
381
+ self.server_config.server_params.read().workers == 1
382
+ }
383
+
358
384
  pub(crate) async fn serve_connection(
359
385
  &self,
360
386
  stream: IoStream,
@@ -417,12 +443,15 @@ impl SingleMode {
417
443
  /// Attempts to reload the config "live"
418
444
  /// Not that when running in single mode this will not unload
419
445
  /// old code. If you need a clean restart, use the `restart` (SIGHUP) method instead
420
- pub fn reload(&self) -> Result<()> {
421
- if !self.server_config.check_config() {
446
+ pub async fn reload(&self) -> Result<()> {
447
+ if !self.server_config.check_config().await {
422
448
  return Ok(());
423
449
  }
424
450
  let should_reexec = self.server_config.clone().reload(false)?;
425
451
  if should_reexec {
452
+ if self.is_single_mode() {
453
+ self.invoke_hook("before_restart");
454
+ }
426
455
  self.server_config.dup_fds()?;
427
456
  self.server_config.reload_exec()?;
428
457
  }
@@ -432,11 +461,19 @@ impl SingleMode {
432
461
  Ok(())
433
462
  }
434
463
 
464
+ pub fn invoke_hook(&self, hook_name: &str) {
465
+ if let Some(hook) = self.server_config.server_params.read().hooks.get(hook_name) {
466
+ call_with_gvl(|_| hook.call::<_, Value>(()).ok());
467
+ }
468
+ }
435
469
  /// Restart the server while keeping connections open.
436
- pub fn restart(&self) -> Result<()> {
437
- if !self.server_config.check_config() {
470
+ pub async fn restart(&self) -> Result<()> {
471
+ if !self.server_config.check_config().await {
438
472
  return Ok(());
439
473
  }
474
+ if self.is_single_mode() {
475
+ self.invoke_hook("before_restart");
476
+ }
440
477
  self.server_config.dup_fds()?;
441
478
  self.server_config.reload_exec()?;
442
479
  Ok(())
@@ -49,6 +49,7 @@ fn receive_signal(signum: i32, _: sighandler_t) {
49
49
  pub fn reset_signal_handlers() -> bool {
50
50
  SIGINT_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
51
51
  SHUTDOWN_REQUESTED.store(false, std::sync::atomic::Ordering::SeqCst);
52
+
52
53
  unsafe {
53
54
  libc::signal(libc::SIGTERM, receive_signal as usize);
54
55
  libc::signal(libc::SIGINT, receive_signal as usize);
@@ -10,6 +10,7 @@ use std::sync::atomic::AtomicUsize;
10
10
  use std::sync::atomic::Ordering;
11
11
  use std::task::Context;
12
12
  use std::task::Poll;
13
+ use tracing::debug;
13
14
 
14
15
  /// Custom error to indicate that the maximum body size was exceeded.
15
16
  #[derive(Debug)]
@@ -71,6 +72,11 @@ where
71
72
  Ok(data) => {
72
73
  let len = data.remaining();
73
74
  self.current += len;
75
+ debug!(
76
+ target: "option::max_body",
77
+ "current: {}, limit: {}",
78
+ self.current, self.limit.load(Ordering::Relaxed)
79
+ );
74
80
  if self.current > self.limit.load(Ordering::Relaxed) {
75
81
  Poll::Ready(Some(Err(Box::new(MaxBodySizeReached))))
76
82
  } else {
@@ -1,4 +1,5 @@
1
1
  use async_channel::Sender;
2
+ use itsi_error::ItsiError;
2
3
  use itsi_rb_helpers::{
3
4
  call_with_gvl, call_without_gvl, create_ruby_thread, kill_threads, HeapValue,
4
5
  };
@@ -159,7 +160,7 @@ impl ThreadWorker {
159
160
  pub fn poll_shutdown(&self, deadline: Instant) -> bool {
160
161
  if let Some(thread) = self.thread.read().deref() {
161
162
  if Instant::now() > deadline {
162
- warn!("Worker shutdown timed out. Killing thread {:?}", thread);
163
+ debug!("Worker shutdown timed out. Killing thread {:?}", thread);
163
164
  self.terminated.store(true, Ordering::SeqCst);
164
165
  kill_threads(vec![thread.as_value()]);
165
166
  }
@@ -198,6 +199,9 @@ impl ThreadWorker {
198
199
  self_ref.accept_loop(params, name, receiver, terminated);
199
200
  }
200
201
  })
202
+ .ok_or_else(|| {
203
+ ItsiError::InternalServerError("Failed to create Ruby thread".to_owned())
204
+ })?
201
205
  .into(),
202
206
  );
203
207
  Ok::<(), magnus::Error>(())
@@ -1,5 +1,5 @@
1
1
  use crate::default_responses::{NOT_FOUND_RESPONSE, TIMEOUT_RESPONSE};
2
- use crate::ruby_types::itsi_server::itsi_server_config::ServerParams;
2
+ use crate::ruby_types::itsi_server::itsi_server_config::{ItsiServerTokenPreference, ServerParams};
3
3
  use crate::server::binds::listener::ListenerInfo;
4
4
  use crate::server::http_message_types::{ConversionExt, HttpResponse, RequestExt, ResponseFormat};
5
5
  use crate::server::lifecycle_event::LifecycleEvent;
@@ -18,6 +18,7 @@ use itsi_error::ItsiError;
18
18
  use regex::Regex;
19
19
  use std::sync::atomic::{AtomicBool, Ordering};
20
20
  use std::sync::OnceLock;
21
+ use tracing::error;
21
22
 
22
23
  use std::{future::Future, ops::Deref, pin::Pin, sync::Arc};
23
24
  use tokio::sync::watch::{self};
@@ -153,6 +154,10 @@ impl HttpRequestContext {
153
154
  }
154
155
  }
155
156
 
157
+ const SERVER_TOKEN_VERSION: HeaderValue =
158
+ HeaderValue::from_static(concat!("Itsi/", env!("CARGO_PKG_VERSION")));
159
+ const SERVER_TOKEN_NAME: HeaderValue = HeaderValue::from_static("Itsi");
160
+
156
161
  impl Service<Request<Incoming>> for ItsiHttpService {
157
162
  type Response = HttpResponse;
158
163
  type Error = ItsiError;
@@ -195,7 +200,10 @@ impl Service<Request<Incoming>> for ItsiHttpService {
195
200
  depth = index;
196
201
  break;
197
202
  }
198
- Err(e) => return Err(e.into()),
203
+ Err(e) => {
204
+ error!("Middleware error: {}", e);
205
+ break;
206
+ }
199
207
  }
200
208
  }
201
209
 
@@ -208,6 +216,16 @@ impl Service<Request<Incoming>> for ItsiHttpService {
208
216
  resp = elm.after(resp, &mut context).await;
209
217
  }
210
218
 
219
+ match params.itsi_server_token_preference {
220
+ ItsiServerTokenPreference::Version => {
221
+ resp.headers_mut().insert("Server", SERVER_TOKEN_VERSION);
222
+ }
223
+ ItsiServerTokenPreference::Name => {
224
+ resp.headers_mut().insert("Server", SERVER_TOKEN_NAME);
225
+ }
226
+ ItsiServerTokenPreference::None => {}
227
+ }
228
+
211
229
  Ok(resp)
212
230
  };
213
231
 
@@ -5,10 +5,12 @@ use redis::{Client, RedisError, Script};
5
5
  use serde::Deserialize;
6
6
  use std::any::Any;
7
7
  use std::collections::{HashMap, HashSet};
8
+ use std::result::Result;
8
9
  use std::sync::{Arc, LazyLock, Mutex};
9
10
  use std::time::{Duration, Instant};
10
11
  use tokio::sync::{Mutex as AsyncMutex, RwLock};
11
12
  use tokio::time::timeout;
13
+ use tracing::warn;
12
14
  use url::Url;
13
15
 
14
16
  #[derive(Debug)]
@@ -109,10 +111,19 @@ impl RedisRateLimiter {
109
111
  // Create the Lua script once when initializing the rate limiter
110
112
  let increment_script = Script::new(
111
113
  r#"
114
+ -- Increment the counter
112
115
  local current = redis.call('INCR', KEYS[1])
113
- if redis.call('TTL', KEYS[1]) < 0 then
114
- redis.call('EXPIRE', KEYS[1], ARGV[1])
116
+
117
+ -- Fetch existing TTL
118
+ local ttl = redis.call('TTL', KEYS[1])
119
+ if ttl < 0 then
120
+ -- If no TTL is set, apply the window interval
121
+ local window = tonumber(ARGV[1])
122
+ redis.call('EXPIRE', KEYS[1], window)
123
+ ttl = window
115
124
  end
125
+
126
+ -- Return both the current count and remaining TTL
116
127
  return { current, ttl }
117
128
  "#,
118
129
  );
@@ -135,14 +146,14 @@ impl RedisRateLimiter {
135
146
  let mut connection = (*self.connection).clone();
136
147
 
137
148
  // Set the ban with the reason as the value
138
- let _: () = redis::cmd("SET")
149
+ let _: Result<(), RedisError> = redis::cmd("SET")
139
150
  .arg(&ban_key)
140
151
  .arg(reason)
141
152
  .arg("EX")
142
153
  .arg(timeout_secs)
143
154
  .query_async(&mut connection)
144
155
  .await
145
- .map_err(RateLimitError::RedisError)?;
156
+ .inspect_err(|e| warn!("Exception banning IP {:?}", e));
146
157
 
147
158
  Ok(())
148
159
  }
@@ -1,9 +1,10 @@
1
1
  use crate::{
2
- default_responses::{INTERNAL_SERVER_ERROR_RESPONSE, NOT_FOUND_RESPONSE},
2
+ default_responses::NOT_FOUND_RESPONSE,
3
3
  prelude::*,
4
4
  server::{
5
5
  http_message_types::{HttpRequest, HttpResponse, RequestExt, ResponseFormat},
6
6
  middleware_stack::ErrorResponse,
7
+ redirect_type::RedirectType,
7
8
  },
8
9
  };
9
10
  use base64::{engine::general_purpose, Engine};
@@ -46,6 +47,7 @@ pub static ROOT_STATIC_FILE_SERVER: LazyLock<StaticFileServer> = LazyLock::new(|
46
47
  recheck_interval: Duration::from_secs(1),
47
48
  try_html_extension: true,
48
49
  auto_index: true,
50
+ headers: None,
49
51
  not_found_behavior: NotFoundBehavior::Error(ErrorResponse::not_found()),
50
52
  serve_hidden_files: false,
51
53
  allowed_extensions: vec!["html".to_string(), "css".to_string(), "js".to_string()],
@@ -56,6 +58,7 @@ pub static ROOT_STATIC_FILE_SERVER: LazyLock<StaticFileServer> = LazyLock::new(|
56
58
  #[derive(Debug, Clone, Deserialize)]
57
59
  pub struct Redirect {
58
60
  pub to: String,
61
+ pub r#type: RedirectType,
59
62
  }
60
63
 
61
64
  #[derive(Debug, Clone, Deserialize)]
@@ -68,8 +71,6 @@ pub enum NotFoundBehavior {
68
71
  IndexFile(PathBuf),
69
72
  #[serde(rename = "redirect")]
70
73
  Redirect(Redirect),
71
- #[serde(rename = "internal_server_error")]
72
- InternalServerError,
73
74
  }
74
75
 
75
76
  #[derive(Debug, Clone)]
@@ -81,6 +82,7 @@ pub struct StaticFileServerConfig {
81
82
  pub try_html_extension: bool,
82
83
  pub auto_index: bool,
83
84
  pub not_found_behavior: NotFoundBehavior,
85
+ pub headers: Option<HashMap<String, String>>,
84
86
  pub serve_hidden_files: bool,
85
87
  pub allowed_extensions: Vec<String>,
86
88
  }
@@ -113,23 +115,26 @@ struct CacheEntry {
113
115
  }
114
116
 
115
117
  impl CacheEntry {
116
- pub fn suggest_content_for(&self, supported_encodings: &[HeaderValue]) -> (Arc<Bytes>, &str) {
118
+ pub fn suggest_content_for(
119
+ &self,
120
+ supported_encodings: &[HeaderValue],
121
+ ) -> (Arc<Bytes>, Option<&str>) {
117
122
  for encoding_header in supported_encodings {
118
123
  if let Ok(header_value) = encoding_header.to_str() {
119
124
  for header_value in header_value.split(",").map(|hv| hv.trim()) {
120
125
  for algo in header_value.split(";").map(|hv| hv.trim()) {
121
126
  match algo {
122
127
  "zstd" if self.zstd_encoded.is_some() => {
123
- return (self.zstd_encoded.clone().unwrap(), "zstd")
128
+ return (self.zstd_encoded.clone().unwrap(), Some("zstd"))
124
129
  }
125
130
  "gzip" if self.gzip_encoded.is_some() => {
126
- return (self.gzip_encoded.clone().unwrap(), "gzip")
131
+ return (self.gzip_encoded.clone().unwrap(), Some("gzip"))
127
132
  }
128
133
  "br" if self.br_encoded.is_some() => {
129
- return (self.br_encoded.clone().unwrap(), "br")
134
+ return (self.br_encoded.clone().unwrap(), Some("br"))
130
135
  }
131
136
  "deflate" if self.deflate_encoded.is_some() => {
132
- return (self.deflate_encoded.clone().unwrap(), "deflate")
137
+ return (self.deflate_encoded.clone().unwrap(), Some("deflate"))
133
138
  }
134
139
  _ => {}
135
140
  }
@@ -137,7 +142,7 @@ impl CacheEntry {
137
142
  }
138
143
  }
139
144
  }
140
- (self.content.clone(), "identity")
145
+ (self.content.clone(), None)
141
146
  }
142
147
  }
143
148
 
@@ -304,15 +309,10 @@ impl StaticFileServer {
304
309
  .await
305
310
  }
306
311
  NotFoundBehavior::Redirect(redirect) => Response::builder()
307
- .status(StatusCode::MOVED_PERMANENTLY)
312
+ .status(redirect.r#type.status_code())
308
313
  .header(header::LOCATION, redirect.to)
309
314
  .body(BoxBody::new(Full::new(Bytes::new())))
310
315
  .unwrap(),
311
- NotFoundBehavior::InternalServerError => {
312
- INTERNAL_SERVER_ERROR_RESPONSE
313
- .to_http_response(request.accept().into())
314
- .await
315
- }
316
316
  },
317
317
  })
318
318
  }
@@ -434,8 +434,8 @@ impl StaticFileServer {
434
434
 
435
435
  // No valid cached entry, resolve the key to a file path
436
436
  let decoded_key = percent_decode_str(key).decode_utf8_lossy();
437
- let normalized_path =
438
- normalize_path(decoded_key).ok_or(NotFoundBehavior::InternalServerError)?;
437
+ let normalized_path = normalize_path(decoded_key)
438
+ .ok_or(NotFoundBehavior::Error(NOT_FOUND_RESPONSE.clone()))?;
439
439
 
440
440
  if !self.config.serve_hidden_files
441
441
  && normalized_path
@@ -449,6 +449,7 @@ impl StaticFileServer {
449
449
 
450
450
  let mut full_path = self.config.root_dir.clone();
451
451
  full_path.push(normalized_path);
452
+ debug!("Resolving path {:?}", full_path);
452
453
  // Check if path exists and is a file
453
454
  match tokio::fs::metadata(&full_path).await {
454
455
  Ok(metadata) => {
@@ -492,7 +493,9 @@ impl StaticFileServer {
492
493
  // Check for case insensitive index.html
493
494
  let entries = match tokio::fs::read_dir(&full_path).await {
494
495
  Ok(entries) => entries,
495
- Err(_) => return Err(NotFoundBehavior::InternalServerError),
496
+ Err(_) => {
497
+ return Err(NotFoundBehavior::Error(NOT_FOUND_RESPONSE.clone()))
498
+ }
496
499
  };
497
500
 
498
501
  tokio::pin!(entries);
@@ -558,6 +561,7 @@ impl StaticFileServer {
558
561
  }
559
562
  Err(_) => {
560
563
  // Path doesn't exist, try with .html extension if configured
564
+ debug!("Path doesn't exist");
561
565
  if self.config.try_html_extension {
562
566
  let mut html_path = full_path.clone();
563
567
  html_path.set_extension("html");
@@ -754,6 +758,7 @@ impl StaticFileServer {
754
758
  (end_idx - start) as usize,
755
759
  last_modified,
756
760
  content_range,
761
+ &self.headers,
757
762
  self.stream_file_range(file, start, end_idx).await.unwrap(),
758
763
  )
759
764
  } else {
@@ -765,6 +770,7 @@ impl StaticFileServer {
765
770
  content_length as usize,
766
771
  last_modified,
767
772
  content_range,
773
+ &self.headers,
768
774
  self.stream_file(file).await.unwrap(),
769
775
  )
770
776
  }
@@ -863,6 +869,7 @@ impl StaticFileServer {
863
869
  range_bytes.len(),
864
870
  cache_entry.last_modified,
865
871
  content_range,
872
+ &self.headers,
866
873
  BoxBody::new(Full::new(range_bytes)),
867
874
  )
868
875
  } else {
@@ -871,12 +878,13 @@ impl StaticFileServer {
871
878
  let body = build_ok_body(content);
872
879
  build_file_response(
873
880
  status,
874
- Some(encoding),
881
+ encoding,
875
882
  Some(&cache_entry.etag),
876
883
  get_mime_type(path),
877
884
  content_length as usize,
878
885
  cache_entry.last_modified,
879
886
  content_range,
887
+ &self.headers,
880
888
  body,
881
889
  )
882
890
  }
@@ -947,6 +955,7 @@ fn build_file_response(
947
955
  content_length: usize,
948
956
  last_modified: SystemTime,
949
957
  range_header: Option<String>,
958
+ headers: &Option<HashMap<String, String>>,
950
959
  body: BoxBody<Bytes, Infallible>,
951
960
  ) -> http::Response<BoxBody<Bytes, Infallible>> {
952
961
  let mut builder = Response::builder()
@@ -966,6 +975,11 @@ fn build_file_response(
966
975
  if let Some(range) = range_header {
967
976
  builder = builder.header(CONTENT_RANGE, range);
968
977
  }
978
+ if let Some(headers) = headers {
979
+ for (key, value) in headers {
980
+ builder = builder.header(key, value);
981
+ }
982
+ }
969
983
 
970
984
  builder.body(body).unwrap()
971
985
  }