itsi 0.2.3 → 0.2.4

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 (460) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +2 -2
  3. data/README.md +1 -1
  4. data/Rakefile +2 -2
  5. data/crates/itsi_scheduler/Cargo.toml +1 -1
  6. data/crates/itsi_server/Cargo.toml +1 -1
  7. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +28 -11
  8. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +1 -1
  9. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +1 -2
  10. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +14 -2
  11. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +86 -41
  12. data/crates/itsi_server/src/services/itsi_http_service.rs +46 -35
  13. data/crates/itsi_server/src/services/static_file_server.rs +31 -3
  14. data/examples/api_with_schema_and_controllers/Itsi.rb +35 -0
  15. data/examples/api_with_schema_and_controllers/README.md +10 -0
  16. data/examples/api_with_schema_and_controllers/controllers.rb +66 -0
  17. data/examples/api_with_schema_and_controllers/schemas.rb +25 -0
  18. data/examples/file_server/Itsi.rb +30 -0
  19. data/examples/file_server/README.md +3 -0
  20. data/examples/file_server/about.html +80 -0
  21. data/examples/file_server/admin/secrets.txt +1 -0
  22. data/examples/file_server/fairytale.txt +33 -0
  23. data/examples/file_server/itsi-server-100.png +0 -0
  24. data/examples/file_server/subdirectory/movies.csv +21 -0
  25. data/examples/helpers/datastore.rb +84 -0
  26. data/examples/media_server/Itsi.rb +5 -0
  27. data/examples/media_server/README.md +3 -0
  28. data/examples/media_server/images/itsi-server-100.png +0 -0
  29. data/examples/media_server/index.html +82 -0
  30. data/examples/multi_rack_rails_sinatra/Gemfile +66 -0
  31. data/examples/multi_rack_rails_sinatra/Gemfile.lock +416 -0
  32. data/examples/multi_rack_rails_sinatra/Itsi.rb +44 -0
  33. data/examples/multi_rack_rails_sinatra/README.md +7 -0
  34. data/examples/multi_rack_rails_sinatra/index.html +119 -0
  35. data/examples/multi_rack_rails_sinatra/rails_subapp/.dockerignore +51 -0
  36. data/examples/multi_rack_rails_sinatra/rails_subapp/.gitattributes +9 -0
  37. data/examples/multi_rack_rails_sinatra/rails_subapp/.github/dependabot.yml +12 -0
  38. data/examples/multi_rack_rails_sinatra/rails_subapp/.github/workflows/ci.yml +90 -0
  39. data/examples/multi_rack_rails_sinatra/rails_subapp/.gitignore +34 -0
  40. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/docker-setup.sample +3 -0
  41. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/post-app-boot.sample +3 -0
  42. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/post-deploy.sample +14 -0
  43. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/post-proxy-reboot.sample +3 -0
  44. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-app-boot.sample +3 -0
  45. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-build.sample +51 -0
  46. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-connect.sample +47 -0
  47. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-deploy.sample +109 -0
  48. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  49. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/secrets +17 -0
  50. data/examples/multi_rack_rails_sinatra/rails_subapp/.rubocop.yml +8 -0
  51. data/examples/multi_rack_rails_sinatra/rails_subapp/.ruby-version +1 -0
  52. data/examples/multi_rack_rails_sinatra/rails_subapp/Dockerfile +72 -0
  53. data/examples/multi_rack_rails_sinatra/rails_subapp/Gemfile +66 -0
  54. data/examples/multi_rack_rails_sinatra/rails_subapp/Gemfile.lock +416 -0
  55. data/examples/multi_rack_rails_sinatra/rails_subapp/README.md +24 -0
  56. data/examples/multi_rack_rails_sinatra/rails_subapp/Rakefile +6 -0
  57. data/examples/multi_rack_rails_sinatra/rails_subapp/app/assets/images/.keep +0 -0
  58. data/examples/multi_rack_rails_sinatra/rails_subapp/app/assets/stylesheets/application.css +161 -0
  59. data/examples/multi_rack_rails_sinatra/rails_subapp/app/controllers/application_controller.rb +4 -0
  60. data/examples/multi_rack_rails_sinatra/rails_subapp/app/controllers/articles_controller.rb +70 -0
  61. data/examples/multi_rack_rails_sinatra/rails_subapp/app/controllers/concerns/.keep +0 -0
  62. data/examples/multi_rack_rails_sinatra/rails_subapp/app/controllers/home_controller.rb +4 -0
  63. data/examples/multi_rack_rails_sinatra/rails_subapp/app/helpers/application_helper.rb +2 -0
  64. data/examples/multi_rack_rails_sinatra/rails_subapp/app/helpers/articles_helper.rb +2 -0
  65. data/examples/multi_rack_rails_sinatra/rails_subapp/app/helpers/home_helper.rb +2 -0
  66. data/examples/multi_rack_rails_sinatra/rails_subapp/app/javascript/application.js +3 -0
  67. data/examples/multi_rack_rails_sinatra/rails_subapp/app/javascript/controllers/application.js +9 -0
  68. data/examples/multi_rack_rails_sinatra/rails_subapp/app/javascript/controllers/hello_controller.js +7 -0
  69. data/examples/multi_rack_rails_sinatra/rails_subapp/app/javascript/controllers/index.js +4 -0
  70. data/examples/multi_rack_rails_sinatra/rails_subapp/app/jobs/application_job.rb +7 -0
  71. data/examples/multi_rack_rails_sinatra/rails_subapp/app/mailers/application_mailer.rb +4 -0
  72. data/examples/multi_rack_rails_sinatra/rails_subapp/app/models/application_record.rb +3 -0
  73. data/examples/multi_rack_rails_sinatra/rails_subapp/app/models/article.rb +2 -0
  74. data/examples/multi_rack_rails_sinatra/rails_subapp/app/models/concerns/.keep +0 -0
  75. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/_article.html.erb +14 -0
  76. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/_article.json.jbuilder +2 -0
  77. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/_form.html.erb +30 -0
  78. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/edit.html.erb +12 -0
  79. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/index.html.erb +29 -0
  80. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/index.json.jbuilder +1 -0
  81. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/new.html.erb +11 -0
  82. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/show.html.erb +10 -0
  83. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/show.json.jbuilder +1 -0
  84. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/home/index.html.erb +8 -0
  85. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/layouts/application.html.erb +34 -0
  86. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/layouts/mailer.html.erb +13 -0
  87. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/layouts/mailer.text.erb +1 -0
  88. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/pwa/manifest.json.erb +22 -0
  89. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/pwa/service-worker.js +26 -0
  90. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/brakeman +7 -0
  91. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/bundle +109 -0
  92. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/dev +2 -0
  93. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/docker-entrypoint +14 -0
  94. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/importmap +4 -0
  95. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/jobs +6 -0
  96. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/kamal +27 -0
  97. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/rails +4 -0
  98. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/rake +4 -0
  99. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/rubocop +8 -0
  100. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/setup +34 -0
  101. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/thrust +5 -0
  102. data/examples/multi_rack_rails_sinatra/rails_subapp/config/application.rb +27 -0
  103. data/examples/multi_rack_rails_sinatra/rails_subapp/config/boot.rb +4 -0
  104. data/examples/multi_rack_rails_sinatra/rails_subapp/config/cable.yml +17 -0
  105. data/examples/multi_rack_rails_sinatra/rails_subapp/config/cache.yml +16 -0
  106. data/examples/multi_rack_rails_sinatra/rails_subapp/config/credentials.yml.enc +1 -0
  107. data/examples/multi_rack_rails_sinatra/rails_subapp/config/database.yml +41 -0
  108. data/examples/multi_rack_rails_sinatra/rails_subapp/config/deploy.yml +116 -0
  109. data/examples/multi_rack_rails_sinatra/rails_subapp/config/environment.rb +5 -0
  110. data/examples/multi_rack_rails_sinatra/rails_subapp/config/environments/development.rb +72 -0
  111. data/examples/multi_rack_rails_sinatra/rails_subapp/config/environments/production.rb +90 -0
  112. data/examples/multi_rack_rails_sinatra/rails_subapp/config/environments/test.rb +53 -0
  113. data/examples/multi_rack_rails_sinatra/rails_subapp/config/importmap.rb +7 -0
  114. data/examples/multi_rack_rails_sinatra/rails_subapp/config/initializers/assets.rb +7 -0
  115. data/examples/multi_rack_rails_sinatra/rails_subapp/config/initializers/content_security_policy.rb +25 -0
  116. data/examples/multi_rack_rails_sinatra/rails_subapp/config/initializers/filter_parameter_logging.rb +8 -0
  117. data/examples/multi_rack_rails_sinatra/rails_subapp/config/initializers/inflections.rb +16 -0
  118. data/examples/multi_rack_rails_sinatra/rails_subapp/config/locales/en.yml +31 -0
  119. data/examples/multi_rack_rails_sinatra/rails_subapp/config/puma.rb +41 -0
  120. data/examples/multi_rack_rails_sinatra/rails_subapp/config/queue.yml +18 -0
  121. data/examples/multi_rack_rails_sinatra/rails_subapp/config/recurring.yml +10 -0
  122. data/examples/multi_rack_rails_sinatra/rails_subapp/config/routes.rb +17 -0
  123. data/examples/multi_rack_rails_sinatra/rails_subapp/config/storage.yml +34 -0
  124. data/examples/multi_rack_rails_sinatra/rails_subapp/config.ru +6 -0
  125. data/examples/multi_rack_rails_sinatra/rails_subapp/db/cable_schema.rb +11 -0
  126. data/examples/multi_rack_rails_sinatra/rails_subapp/db/cache_schema.rb +14 -0
  127. data/examples/multi_rack_rails_sinatra/rails_subapp/db/migrate/20250422211855_create_articles.rb +10 -0
  128. data/examples/multi_rack_rails_sinatra/rails_subapp/db/queue_schema.rb +129 -0
  129. data/examples/multi_rack_rails_sinatra/rails_subapp/db/schema.rb +20 -0
  130. data/examples/multi_rack_rails_sinatra/rails_subapp/db/seeds.rb +9 -0
  131. data/examples/multi_rack_rails_sinatra/rails_subapp/lib/tasks/.keep +0 -0
  132. data/examples/multi_rack_rails_sinatra/rails_subapp/log/.keep +0 -0
  133. data/examples/multi_rack_rails_sinatra/rails_subapp/public/400.html +114 -0
  134. data/examples/multi_rack_rails_sinatra/rails_subapp/public/404.html +114 -0
  135. data/examples/multi_rack_rails_sinatra/rails_subapp/public/406-unsupported-browser.html +114 -0
  136. data/examples/multi_rack_rails_sinatra/rails_subapp/public/422.html +114 -0
  137. data/examples/multi_rack_rails_sinatra/rails_subapp/public/500.html +114 -0
  138. data/examples/multi_rack_rails_sinatra/rails_subapp/public/icon.png +0 -0
  139. data/examples/multi_rack_rails_sinatra/rails_subapp/public/icon.svg +3 -0
  140. data/examples/multi_rack_rails_sinatra/rails_subapp/public/robots.txt +1 -0
  141. data/examples/multi_rack_rails_sinatra/rails_subapp/script/.keep +0 -0
  142. data/examples/multi_rack_rails_sinatra/rails_subapp/storage/.keep +0 -0
  143. data/examples/multi_rack_rails_sinatra/rails_subapp/test/application_system_test_case.rb +5 -0
  144. data/examples/multi_rack_rails_sinatra/rails_subapp/test/controllers/.keep +0 -0
  145. data/examples/multi_rack_rails_sinatra/rails_subapp/test/controllers/articles_controller_test.rb +48 -0
  146. data/examples/multi_rack_rails_sinatra/rails_subapp/test/controllers/home_controller_test.rb +8 -0
  147. data/examples/multi_rack_rails_sinatra/rails_subapp/test/fixtures/articles.yml +9 -0
  148. data/examples/multi_rack_rails_sinatra/rails_subapp/test/fixtures/files/.keep +0 -0
  149. data/examples/multi_rack_rails_sinatra/rails_subapp/test/helpers/.keep +0 -0
  150. data/examples/multi_rack_rails_sinatra/rails_subapp/test/integration/.keep +0 -0
  151. data/examples/multi_rack_rails_sinatra/rails_subapp/test/mailers/.keep +0 -0
  152. data/examples/multi_rack_rails_sinatra/rails_subapp/test/models/.keep +0 -0
  153. data/examples/multi_rack_rails_sinatra/rails_subapp/test/models/article_test.rb +7 -0
  154. data/examples/multi_rack_rails_sinatra/rails_subapp/test/system/.keep +0 -0
  155. data/examples/multi_rack_rails_sinatra/rails_subapp/test/system/articles_test.rb +43 -0
  156. data/examples/multi_rack_rails_sinatra/rails_subapp/test/test_helper.rb +15 -0
  157. data/examples/multi_rack_rails_sinatra/rails_subapp/tmp/.keep +0 -0
  158. data/examples/multi_rack_rails_sinatra/rails_subapp/tmp/pids/.keep +0 -0
  159. data/examples/multi_rack_rails_sinatra/rails_subapp/tmp/storage/.keep +0 -0
  160. data/examples/multi_rack_rails_sinatra/rails_subapp/vendor/.keep +0 -0
  161. data/examples/multi_rack_rails_sinatra/rails_subapp/vendor/javascript/.keep +0 -0
  162. data/examples/multi_rack_rails_sinatra/sinatra_subapp/config.ru +2 -0
  163. data/examples/multi_rack_rails_sinatra/sinatra_subapp/my_app.rb +96 -0
  164. data/examples/rails_with_static_assets/.dockerignore +51 -0
  165. data/examples/rails_with_static_assets/.gitattributes +9 -0
  166. data/examples/rails_with_static_assets/.github/dependabot.yml +12 -0
  167. data/examples/rails_with_static_assets/.github/workflows/ci.yml +90 -0
  168. data/examples/rails_with_static_assets/.gitignore +34 -0
  169. data/examples/rails_with_static_assets/.kamal/hooks/docker-setup.sample +3 -0
  170. data/examples/rails_with_static_assets/.kamal/hooks/post-app-boot.sample +3 -0
  171. data/examples/rails_with_static_assets/.kamal/hooks/post-deploy.sample +14 -0
  172. data/examples/rails_with_static_assets/.kamal/hooks/post-proxy-reboot.sample +3 -0
  173. data/examples/rails_with_static_assets/.kamal/hooks/pre-app-boot.sample +3 -0
  174. data/examples/rails_with_static_assets/.kamal/hooks/pre-build.sample +51 -0
  175. data/examples/rails_with_static_assets/.kamal/hooks/pre-connect.sample +47 -0
  176. data/examples/rails_with_static_assets/.kamal/hooks/pre-deploy.sample +109 -0
  177. data/examples/rails_with_static_assets/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  178. data/examples/rails_with_static_assets/.kamal/secrets +17 -0
  179. data/examples/rails_with_static_assets/.rubocop.yml +8 -0
  180. data/examples/rails_with_static_assets/.ruby-version +1 -0
  181. data/examples/rails_with_static_assets/Dockerfile +72 -0
  182. data/examples/rails_with_static_assets/Gemfile +66 -0
  183. data/examples/rails_with_static_assets/Gemfile.lock +416 -0
  184. data/examples/rails_with_static_assets/Itsi.rb +10 -0
  185. data/examples/rails_with_static_assets/README.md +14 -0
  186. data/examples/rails_with_static_assets/Rakefile +6 -0
  187. data/examples/rails_with_static_assets/app/assets/images/.keep +0 -0
  188. data/examples/rails_with_static_assets/app/assets/stylesheets/application.css +161 -0
  189. data/examples/rails_with_static_assets/app/controllers/application_controller.rb +4 -0
  190. data/examples/rails_with_static_assets/app/controllers/articles_controller.rb +70 -0
  191. data/examples/rails_with_static_assets/app/controllers/concerns/.keep +0 -0
  192. data/examples/rails_with_static_assets/app/controllers/home_controller.rb +4 -0
  193. data/examples/rails_with_static_assets/app/helpers/application_helper.rb +2 -0
  194. data/examples/rails_with_static_assets/app/helpers/articles_helper.rb +2 -0
  195. data/examples/rails_with_static_assets/app/helpers/home_helper.rb +2 -0
  196. data/examples/rails_with_static_assets/app/javascript/application.js +3 -0
  197. data/examples/rails_with_static_assets/app/javascript/controllers/application.js +9 -0
  198. data/examples/rails_with_static_assets/app/javascript/controllers/hello_controller.js +7 -0
  199. data/examples/rails_with_static_assets/app/javascript/controllers/index.js +4 -0
  200. data/examples/rails_with_static_assets/app/jobs/application_job.rb +7 -0
  201. data/examples/rails_with_static_assets/app/mailers/application_mailer.rb +4 -0
  202. data/examples/rails_with_static_assets/app/models/application_record.rb +3 -0
  203. data/examples/rails_with_static_assets/app/models/article.rb +2 -0
  204. data/examples/rails_with_static_assets/app/models/concerns/.keep +0 -0
  205. data/examples/rails_with_static_assets/app/views/articles/_article.html.erb +14 -0
  206. data/examples/rails_with_static_assets/app/views/articles/_article.json.jbuilder +2 -0
  207. data/examples/rails_with_static_assets/app/views/articles/_form.html.erb +30 -0
  208. data/examples/rails_with_static_assets/app/views/articles/edit.html.erb +12 -0
  209. data/examples/rails_with_static_assets/app/views/articles/index.html.erb +29 -0
  210. data/examples/rails_with_static_assets/app/views/articles/index.json.jbuilder +1 -0
  211. data/examples/rails_with_static_assets/app/views/articles/new.html.erb +11 -0
  212. data/examples/rails_with_static_assets/app/views/articles/show.html.erb +10 -0
  213. data/examples/rails_with_static_assets/app/views/articles/show.json.jbuilder +1 -0
  214. data/examples/rails_with_static_assets/app/views/home/index.html.erb +8 -0
  215. data/examples/rails_with_static_assets/app/views/layouts/application.html.erb +34 -0
  216. data/examples/rails_with_static_assets/app/views/layouts/mailer.html.erb +13 -0
  217. data/examples/rails_with_static_assets/app/views/layouts/mailer.text.erb +1 -0
  218. data/examples/rails_with_static_assets/app/views/pwa/manifest.json.erb +22 -0
  219. data/examples/rails_with_static_assets/app/views/pwa/service-worker.js +26 -0
  220. data/examples/rails_with_static_assets/bin/brakeman +7 -0
  221. data/examples/rails_with_static_assets/bin/bundle +109 -0
  222. data/examples/rails_with_static_assets/bin/dev +2 -0
  223. data/examples/rails_with_static_assets/bin/docker-entrypoint +14 -0
  224. data/examples/rails_with_static_assets/bin/importmap +4 -0
  225. data/examples/rails_with_static_assets/bin/jobs +6 -0
  226. data/examples/rails_with_static_assets/bin/kamal +27 -0
  227. data/examples/rails_with_static_assets/bin/rails +4 -0
  228. data/examples/rails_with_static_assets/bin/rake +4 -0
  229. data/examples/rails_with_static_assets/bin/rubocop +8 -0
  230. data/examples/rails_with_static_assets/bin/setup +34 -0
  231. data/examples/rails_with_static_assets/bin/thrust +5 -0
  232. data/examples/rails_with_static_assets/config/application.rb +26 -0
  233. data/examples/rails_with_static_assets/config/boot.rb +4 -0
  234. data/examples/rails_with_static_assets/config/cable.yml +17 -0
  235. data/examples/rails_with_static_assets/config/cache.yml +16 -0
  236. data/examples/rails_with_static_assets/config/credentials.yml.enc +1 -0
  237. data/examples/rails_with_static_assets/config/database.yml +41 -0
  238. data/examples/rails_with_static_assets/config/deploy.yml +116 -0
  239. data/examples/rails_with_static_assets/config/environment.rb +5 -0
  240. data/examples/rails_with_static_assets/config/environments/development.rb +74 -0
  241. data/examples/rails_with_static_assets/config/environments/production.rb +90 -0
  242. data/examples/rails_with_static_assets/config/environments/test.rb +53 -0
  243. data/examples/rails_with_static_assets/config/importmap.rb +7 -0
  244. data/examples/rails_with_static_assets/config/initializers/assets.rb +7 -0
  245. data/examples/rails_with_static_assets/config/initializers/content_security_policy.rb +25 -0
  246. data/examples/rails_with_static_assets/config/initializers/filter_parameter_logging.rb +8 -0
  247. data/examples/rails_with_static_assets/config/initializers/inflections.rb +16 -0
  248. data/examples/rails_with_static_assets/config/locales/en.yml +31 -0
  249. data/examples/rails_with_static_assets/config/puma.rb +41 -0
  250. data/examples/rails_with_static_assets/config/queue.yml +18 -0
  251. data/examples/rails_with_static_assets/config/recurring.yml +10 -0
  252. data/examples/rails_with_static_assets/config/routes.rb +17 -0
  253. data/examples/rails_with_static_assets/config/storage.yml +34 -0
  254. data/examples/rails_with_static_assets/config.ru +6 -0
  255. data/examples/rails_with_static_assets/db/cable_schema.rb +11 -0
  256. data/examples/rails_with_static_assets/db/cache_schema.rb +14 -0
  257. data/examples/rails_with_static_assets/db/migrate/20250422211855_create_articles.rb +10 -0
  258. data/examples/rails_with_static_assets/db/queue_schema.rb +129 -0
  259. data/examples/rails_with_static_assets/db/schema.rb +20 -0
  260. data/examples/rails_with_static_assets/db/seeds.rb +9 -0
  261. data/examples/rails_with_static_assets/lib/tasks/.keep +0 -0
  262. data/examples/rails_with_static_assets/log/.keep +0 -0
  263. data/examples/rails_with_static_assets/public/400.html +114 -0
  264. data/examples/rails_with_static_assets/public/404.html +114 -0
  265. data/examples/rails_with_static_assets/public/406-unsupported-browser.html +114 -0
  266. data/examples/rails_with_static_assets/public/422.html +114 -0
  267. data/examples/rails_with_static_assets/public/500.html +114 -0
  268. data/examples/rails_with_static_assets/public/icon.png +0 -0
  269. data/examples/rails_with_static_assets/public/icon.svg +3 -0
  270. data/examples/rails_with_static_assets/public/robots.txt +1 -0
  271. data/examples/rails_with_static_assets/script/.keep +0 -0
  272. data/examples/rails_with_static_assets/storage/.keep +0 -0
  273. data/examples/rails_with_static_assets/test/application_system_test_case.rb +5 -0
  274. data/examples/rails_with_static_assets/test/controllers/.keep +0 -0
  275. data/examples/rails_with_static_assets/test/controllers/articles_controller_test.rb +48 -0
  276. data/examples/rails_with_static_assets/test/controllers/home_controller_test.rb +8 -0
  277. data/examples/rails_with_static_assets/test/fixtures/articles.yml +9 -0
  278. data/examples/rails_with_static_assets/test/fixtures/files/.keep +0 -0
  279. data/examples/rails_with_static_assets/test/helpers/.keep +0 -0
  280. data/examples/rails_with_static_assets/test/integration/.keep +0 -0
  281. data/examples/rails_with_static_assets/test/mailers/.keep +0 -0
  282. data/examples/rails_with_static_assets/test/models/.keep +0 -0
  283. data/examples/rails_with_static_assets/test/models/article_test.rb +7 -0
  284. data/examples/rails_with_static_assets/test/system/.keep +0 -0
  285. data/examples/rails_with_static_assets/test/system/articles_test.rb +43 -0
  286. data/examples/rails_with_static_assets/test/test_helper.rb +15 -0
  287. data/examples/rails_with_static_assets/tmp/.keep +0 -0
  288. data/examples/rails_with_static_assets/tmp/pids/.keep +0 -0
  289. data/examples/rails_with_static_assets/tmp/storage/.keep +0 -0
  290. data/examples/rails_with_static_assets/vendor/.keep +0 -0
  291. data/examples/rails_with_static_assets/vendor/javascript/.keep +0 -0
  292. data/examples/reverse_proxy/Itsi.rb +28 -0
  293. data/examples/reverse_proxy/README.md +14 -0
  294. data/examples/reverse_proxy/index.html +119 -0
  295. data/examples/reverse_proxy/rails_subapp/.dockerignore +51 -0
  296. data/examples/reverse_proxy/rails_subapp/.gitattributes +9 -0
  297. data/examples/reverse_proxy/rails_subapp/.github/dependabot.yml +12 -0
  298. data/examples/reverse_proxy/rails_subapp/.github/workflows/ci.yml +90 -0
  299. data/examples/reverse_proxy/rails_subapp/.gitignore +34 -0
  300. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/docker-setup.sample +3 -0
  301. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/post-app-boot.sample +3 -0
  302. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/post-deploy.sample +14 -0
  303. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/post-proxy-reboot.sample +3 -0
  304. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-app-boot.sample +3 -0
  305. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-build.sample +51 -0
  306. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-connect.sample +47 -0
  307. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-deploy.sample +109 -0
  308. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  309. data/examples/reverse_proxy/rails_subapp/.kamal/secrets +17 -0
  310. data/examples/reverse_proxy/rails_subapp/.rubocop.yml +8 -0
  311. data/examples/reverse_proxy/rails_subapp/.ruby-version +1 -0
  312. data/examples/reverse_proxy/rails_subapp/Dockerfile +72 -0
  313. data/examples/reverse_proxy/rails_subapp/Gemfile +66 -0
  314. data/examples/reverse_proxy/rails_subapp/Gemfile.lock +416 -0
  315. data/examples/reverse_proxy/rails_subapp/README.md +24 -0
  316. data/examples/reverse_proxy/rails_subapp/Rakefile +6 -0
  317. data/examples/reverse_proxy/rails_subapp/app/assets/images/.keep +0 -0
  318. data/examples/reverse_proxy/rails_subapp/app/assets/stylesheets/application.css +161 -0
  319. data/examples/reverse_proxy/rails_subapp/app/controllers/application_controller.rb +4 -0
  320. data/examples/reverse_proxy/rails_subapp/app/controllers/articles_controller.rb +70 -0
  321. data/examples/reverse_proxy/rails_subapp/app/controllers/concerns/.keep +0 -0
  322. data/examples/reverse_proxy/rails_subapp/app/controllers/home_controller.rb +4 -0
  323. data/examples/reverse_proxy/rails_subapp/app/helpers/application_helper.rb +2 -0
  324. data/examples/reverse_proxy/rails_subapp/app/helpers/articles_helper.rb +2 -0
  325. data/examples/reverse_proxy/rails_subapp/app/helpers/home_helper.rb +2 -0
  326. data/examples/reverse_proxy/rails_subapp/app/javascript/application.js +3 -0
  327. data/examples/reverse_proxy/rails_subapp/app/javascript/controllers/application.js +9 -0
  328. data/examples/reverse_proxy/rails_subapp/app/javascript/controllers/hello_controller.js +7 -0
  329. data/examples/reverse_proxy/rails_subapp/app/javascript/controllers/index.js +4 -0
  330. data/examples/reverse_proxy/rails_subapp/app/jobs/application_job.rb +7 -0
  331. data/examples/reverse_proxy/rails_subapp/app/mailers/application_mailer.rb +4 -0
  332. data/examples/reverse_proxy/rails_subapp/app/models/application_record.rb +3 -0
  333. data/examples/reverse_proxy/rails_subapp/app/models/article.rb +2 -0
  334. data/examples/reverse_proxy/rails_subapp/app/models/concerns/.keep +0 -0
  335. data/examples/reverse_proxy/rails_subapp/app/views/articles/_article.html.erb +14 -0
  336. data/examples/reverse_proxy/rails_subapp/app/views/articles/_article.json.jbuilder +2 -0
  337. data/examples/reverse_proxy/rails_subapp/app/views/articles/_form.html.erb +30 -0
  338. data/examples/reverse_proxy/rails_subapp/app/views/articles/edit.html.erb +12 -0
  339. data/examples/reverse_proxy/rails_subapp/app/views/articles/index.html.erb +29 -0
  340. data/examples/reverse_proxy/rails_subapp/app/views/articles/index.json.jbuilder +1 -0
  341. data/examples/reverse_proxy/rails_subapp/app/views/articles/new.html.erb +11 -0
  342. data/examples/reverse_proxy/rails_subapp/app/views/articles/show.html.erb +10 -0
  343. data/examples/reverse_proxy/rails_subapp/app/views/articles/show.json.jbuilder +1 -0
  344. data/examples/reverse_proxy/rails_subapp/app/views/home/index.html.erb +8 -0
  345. data/examples/reverse_proxy/rails_subapp/app/views/layouts/application.html.erb +34 -0
  346. data/examples/reverse_proxy/rails_subapp/app/views/layouts/mailer.html.erb +13 -0
  347. data/examples/reverse_proxy/rails_subapp/app/views/layouts/mailer.text.erb +1 -0
  348. data/examples/reverse_proxy/rails_subapp/app/views/pwa/manifest.json.erb +22 -0
  349. data/examples/reverse_proxy/rails_subapp/app/views/pwa/service-worker.js +26 -0
  350. data/examples/reverse_proxy/rails_subapp/bin/brakeman +7 -0
  351. data/examples/reverse_proxy/rails_subapp/bin/bundle +109 -0
  352. data/examples/reverse_proxy/rails_subapp/bin/dev +2 -0
  353. data/examples/reverse_proxy/rails_subapp/bin/docker-entrypoint +14 -0
  354. data/examples/reverse_proxy/rails_subapp/bin/importmap +4 -0
  355. data/examples/reverse_proxy/rails_subapp/bin/jobs +6 -0
  356. data/examples/reverse_proxy/rails_subapp/bin/kamal +27 -0
  357. data/examples/reverse_proxy/rails_subapp/bin/rails +4 -0
  358. data/examples/reverse_proxy/rails_subapp/bin/rake +4 -0
  359. data/examples/reverse_proxy/rails_subapp/bin/rubocop +8 -0
  360. data/examples/reverse_proxy/rails_subapp/bin/setup +34 -0
  361. data/examples/reverse_proxy/rails_subapp/bin/thrust +5 -0
  362. data/examples/reverse_proxy/rails_subapp/config/application.rb +26 -0
  363. data/examples/reverse_proxy/rails_subapp/config/boot.rb +4 -0
  364. data/examples/reverse_proxy/rails_subapp/config/cable.yml +17 -0
  365. data/examples/reverse_proxy/rails_subapp/config/cache.yml +16 -0
  366. data/examples/reverse_proxy/rails_subapp/config/credentials.yml.enc +1 -0
  367. data/examples/reverse_proxy/rails_subapp/config/database.yml +41 -0
  368. data/examples/reverse_proxy/rails_subapp/config/deploy.yml +116 -0
  369. data/examples/reverse_proxy/rails_subapp/config/environment.rb +5 -0
  370. data/examples/reverse_proxy/rails_subapp/config/environments/development.rb +72 -0
  371. data/examples/reverse_proxy/rails_subapp/config/environments/production.rb +90 -0
  372. data/examples/reverse_proxy/rails_subapp/config/environments/test.rb +53 -0
  373. data/examples/reverse_proxy/rails_subapp/config/importmap.rb +7 -0
  374. data/examples/reverse_proxy/rails_subapp/config/initializers/assets.rb +7 -0
  375. data/examples/reverse_proxy/rails_subapp/config/initializers/content_security_policy.rb +25 -0
  376. data/examples/reverse_proxy/rails_subapp/config/initializers/filter_parameter_logging.rb +8 -0
  377. data/examples/reverse_proxy/rails_subapp/config/initializers/inflections.rb +16 -0
  378. data/examples/reverse_proxy/rails_subapp/config/locales/en.yml +31 -0
  379. data/examples/reverse_proxy/rails_subapp/config/puma.rb +41 -0
  380. data/examples/reverse_proxy/rails_subapp/config/queue.yml +18 -0
  381. data/examples/reverse_proxy/rails_subapp/config/recurring.yml +10 -0
  382. data/examples/reverse_proxy/rails_subapp/config/routes.rb +17 -0
  383. data/examples/reverse_proxy/rails_subapp/config/storage.yml +34 -0
  384. data/examples/reverse_proxy/rails_subapp/config.ru +6 -0
  385. data/examples/reverse_proxy/rails_subapp/db/cable_schema.rb +11 -0
  386. data/examples/reverse_proxy/rails_subapp/db/cache_schema.rb +14 -0
  387. data/examples/reverse_proxy/rails_subapp/db/migrate/20250422211855_create_articles.rb +10 -0
  388. data/examples/reverse_proxy/rails_subapp/db/queue_schema.rb +129 -0
  389. data/examples/reverse_proxy/rails_subapp/db/schema.rb +20 -0
  390. data/examples/reverse_proxy/rails_subapp/db/seeds.rb +9 -0
  391. data/examples/reverse_proxy/rails_subapp/lib/tasks/.keep +0 -0
  392. data/examples/reverse_proxy/rails_subapp/log/.keep +0 -0
  393. data/examples/reverse_proxy/rails_subapp/public/400.html +114 -0
  394. data/examples/reverse_proxy/rails_subapp/public/404.html +114 -0
  395. data/examples/reverse_proxy/rails_subapp/public/406-unsupported-browser.html +114 -0
  396. data/examples/reverse_proxy/rails_subapp/public/422.html +114 -0
  397. data/examples/reverse_proxy/rails_subapp/public/500.html +114 -0
  398. data/examples/reverse_proxy/rails_subapp/public/icon.png +0 -0
  399. data/examples/reverse_proxy/rails_subapp/public/icon.svg +3 -0
  400. data/examples/reverse_proxy/rails_subapp/public/robots.txt +1 -0
  401. data/examples/reverse_proxy/rails_subapp/script/.keep +0 -0
  402. data/examples/reverse_proxy/rails_subapp/storage/.keep +0 -0
  403. data/examples/reverse_proxy/rails_subapp/test/application_system_test_case.rb +5 -0
  404. data/examples/reverse_proxy/rails_subapp/test/controllers/.keep +0 -0
  405. data/examples/reverse_proxy/rails_subapp/test/controllers/articles_controller_test.rb +48 -0
  406. data/examples/reverse_proxy/rails_subapp/test/controllers/home_controller_test.rb +8 -0
  407. data/examples/reverse_proxy/rails_subapp/test/fixtures/articles.yml +9 -0
  408. data/examples/reverse_proxy/rails_subapp/test/fixtures/files/.keep +0 -0
  409. data/examples/reverse_proxy/rails_subapp/test/helpers/.keep +0 -0
  410. data/examples/reverse_proxy/rails_subapp/test/integration/.keep +0 -0
  411. data/examples/reverse_proxy/rails_subapp/test/mailers/.keep +0 -0
  412. data/examples/reverse_proxy/rails_subapp/test/models/.keep +0 -0
  413. data/examples/reverse_proxy/rails_subapp/test/models/article_test.rb +7 -0
  414. data/examples/reverse_proxy/rails_subapp/test/system/.keep +0 -0
  415. data/examples/reverse_proxy/rails_subapp/test/system/articles_test.rb +43 -0
  416. data/examples/reverse_proxy/rails_subapp/test/test_helper.rb +15 -0
  417. data/examples/reverse_proxy/rails_subapp/tmp/.keep +0 -0
  418. data/examples/reverse_proxy/rails_subapp/tmp/pids/.keep +0 -0
  419. data/examples/reverse_proxy/rails_subapp/tmp/storage/.keep +0 -0
  420. data/examples/reverse_proxy/rails_subapp/vendor/.keep +0 -0
  421. data/examples/reverse_proxy/rails_subapp/vendor/javascript/.keep +0 -0
  422. data/examples/reverse_proxy/sinatra_subapp/Gemfile +2 -0
  423. data/examples/reverse_proxy/sinatra_subapp/Gemfile.lock +46 -0
  424. data/examples/reverse_proxy/sinatra_subapp/config.ru +2 -0
  425. data/examples/reverse_proxy/sinatra_subapp/my_app.rb +96 -0
  426. data/examples/simple_api/Itsi.rb +127 -0
  427. data/examples/simple_api_no_schema/Itsi.rb +86 -0
  428. data/examples/spa/Itsi.rb +5 -0
  429. data/examples/spa/README.md +3 -0
  430. data/examples/spa/dist/assets/index-2cf7be43.js +40 -0
  431. data/examples/spa/dist/index.html +13 -0
  432. data/examples/spa/index.html +12 -0
  433. data/examples/spa/package-lock.json +1265 -0
  434. data/examples/spa/package.json +18 -0
  435. data/examples/spa/src/App.jsx +49 -0
  436. data/examples/spa/src/main.jsx +6 -0
  437. data/examples/spa/src/pages/About.jsx +9 -0
  438. data/examples/spa/src/pages/Home.jsx +9 -0
  439. data/examples/spa/vite.config.js +9 -0
  440. data/gems/scheduler/Cargo.lock +1 -1
  441. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  442. data/gems/server/Cargo.lock +1 -1
  443. data/gems/server/lib/itsi/server/config/dsl.rb +2 -2
  444. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +0 -4
  445. data/gems/server/lib/itsi/server/config/middleware/location.rb +25 -21
  446. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +15 -14
  447. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +7 -10
  448. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +8 -4
  449. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +14 -0
  450. data/gems/server/lib/itsi/server/config/options/include.rb +1 -1
  451. data/gems/server/lib/itsi/server/rack_interface.rb +1 -1
  452. data/gems/server/lib/itsi/server/route_tester.rb +1 -1
  453. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +25 -0
  454. data/gems/server/lib/itsi/server/version.rb +1 -1
  455. data/gems/server/lib/itsi/server.rb +1 -1
  456. data/gems/server/test/middleware/static_assets.rb +6 -4
  457. data/gems/server/test/middleware/string_rewrite.rb +54 -0
  458. data/gems/server/test/middleware/test_log_requests.rb +13 -1
  459. data/lib/itsi/version.rb +1 -1
  460. metadata +431 -5
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "my-app",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.2.0",
12
+ "react-dom": "^18.2.0"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-react": "^4.0.0",
16
+ "vite": "^4.4.0"
17
+ }
18
+ }
@@ -0,0 +1,49 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import Home from "./pages/Home";
3
+ import About from "./pages/About";
4
+
5
+ export default function App() {
6
+ const [path, setPath] = useState(window.location.pathname);
7
+
8
+ useEffect(() => {
9
+ const onPop = () => setPath(window.location.pathname);
10
+ window.addEventListener("popstate", onPop);
11
+ return () => window.removeEventListener("popstate", onPop);
12
+ }, []);
13
+
14
+ const navigate = (to) => {
15
+ window.history.pushState(null, "", to);
16
+ setPath(to);
17
+ };
18
+
19
+ let Page;
20
+ if (path === "/about") Page = About;
21
+ else Page = Home;
22
+
23
+ return (
24
+ <div style={{ padding: 20, fontFamily: "sans-serif" }}>
25
+ <nav style={{ marginBottom: 20 }}>
26
+ <a
27
+ href="/"
28
+ onClick={(e) => {
29
+ e.preventDefault();
30
+ navigate("/");
31
+ }}
32
+ style={{ marginRight: 10 }}
33
+ >
34
+ Home
35
+ </a>
36
+ <a
37
+ href="/about"
38
+ onClick={(e) => {
39
+ e.preventDefault();
40
+ navigate("/about");
41
+ }}
42
+ >
43
+ About
44
+ </a>
45
+ </nav>
46
+ <Page />
47
+ </div>
48
+ );
49
+ }
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import App from "./App";
4
+
5
+ const rootEl = document.getElementById("root");
6
+ createRoot(rootEl).render(<App />);
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ export default function About() {
3
+ return (
4
+ <div>
5
+ <h1>About</h1>
6
+ <p>This is the About page.</p>
7
+ </div>
8
+ );
9
+ }
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ export default function Home() {
3
+ return (
4
+ <div>
5
+ <h1>Home</h1>
6
+ <p>Welcome to the minimal React SPA.</p>
7
+ </div>
8
+ );
9
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ open: true,
8
+ },
9
+ });
@@ -213,7 +213,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
213
213
 
214
214
  [[package]]
215
215
  name = "itsi-scheduler"
216
- version = "0.2.3"
216
+ version = "0.2.4"
217
217
  dependencies = [
218
218
  "bytes",
219
219
  "derive_more",
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Scheduler
5
- VERSION = "0.2.3"
5
+ VERSION = "0.2.4"
6
6
  end
7
7
  end
@@ -1644,7 +1644,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1644
1644
 
1645
1645
  [[package]]
1646
1646
  name = "itsi-server"
1647
- version = "0.2.3"
1647
+ version = "0.2.4"
1648
1648
  dependencies = [
1649
1649
  "argon2",
1650
1650
  "async-channel",
@@ -52,9 +52,9 @@ module Itsi
52
52
  @accepts = accepts.map { |s| s.is_a?(Regexp) ? s : s.to_s }
53
53
 
54
54
  @options = {
55
- middleware_loaders: [],
55
+ nested_locations: [],
56
56
  middleware_loader: lambda do
57
- @options[:middleware_loaders].each(&:call)
57
+ @options[:nested_locations].each(&:call)
58
58
  @middleware[:app] ||= {}
59
59
  @middleware[:app][:app_proc] = @middleware[:app]&.[](:preloader)&.call || DEFAULT_APP[]
60
60
  [flatten_routes, Config.errors_to_error_lines(errors)]
@@ -84,10 +84,6 @@ module Itsi
84
84
  end
85
85
  else
86
86
  @params[:paths] << "" if @params[:paths].empty?
87
- @params[:paths] = @params[:paths].flat_map do |p|
88
- stripped_trailing = p[/(.*)\/?$/, 1]
89
- [stripped_trailing, stripped_trailing + "/"]
90
- end.uniq
91
87
  location.location(*@params[:paths], methods: @params[:http_methods]) do
92
88
  @middleware[:app] = app
93
89
  end
@@ -27,7 +27,7 @@ module Itsi
27
27
  end
28
28
 
29
29
  attr_accessor :location, :routes, :block, :protocols, :hosts, :ports,
30
- :extensions, :content_types, :accepts, :block
30
+ :extensions, :content_types, :accepts
31
31
 
32
32
  def initialize(location,
33
33
  *routes,
@@ -56,13 +56,13 @@ module Itsi
56
56
  block: block
57
57
  }).to_h
58
58
  @routes = params[:routes].empty? ? ["*"] : params[:routes]
59
- @methods = params[:methods]
60
- @protocols = params[:protocols] | params[:schemes]
61
- @hosts = params[:hosts]
62
- @ports = params[:ports]
63
- @extensions = params[:extensions]
64
- @content_types = params[:content_types]
65
- @accepts = params[:accepts]
59
+ @methods = params[:methods].map { |s| s.is_a?(Regexp) ? s : s.to_s }
60
+ @protocols = (params[:protocols] | params[:schemes]).map { |s| s.is_a?(Regexp) ? s : s.to_s }
61
+ @hosts = params[:hosts].map { |s| s.is_a?(Regexp) ? s : s.to_s }
62
+ @ports = params[:ports].map { |s| s.is_a?(Regexp) ? s : s.to_s }
63
+ @extensions = params[:extensions].map { |s| s.is_a?(Regexp) ? s : s.to_s }
64
+ @content_types = params[:content_types].map { |s| s.is_a?(Regexp) ? s : s.to_s }
65
+ @accepts = params[:accepts].map { |s| s.is_a?(Regexp) ? s : s.to_s }
66
66
  @block = block
67
67
  end
68
68
 
@@ -70,27 +70,31 @@ module Itsi
70
70
  @methods
71
71
  end
72
72
 
73
+ def intersect(a, b)
74
+ return b if a.empty?
75
+ return a if b.empty?
76
+ a & b
77
+ end
78
+
73
79
  def build!
74
80
  build_child = lambda {
75
- location.children << DSL.new(
81
+ child = DSL.new(
76
82
  location,
77
83
  routes: routes,
78
- methods: Array(http_methods) | location.http_methods,
79
- protocols: Array(protocols) | location.protocols,
80
- hosts: Array(hosts) | location.hosts,
81
- ports: Array(ports) | location.ports,
82
- extensions: Array(extensions) | location.extensions,
83
- content_types: Array(content_types) | location.content_types,
84
- accepts: Array(accepts) | location.accepts,
84
+ methods: intersect(http_methods, location.http_methods),
85
+ protocols: intersect(protocols, location.protocols),
86
+ hosts: intersect(hosts, location.hosts),
87
+ ports: intersect(ports, location.ports),
88
+ extensions: intersect(extensions, location.extensions),
89
+ content_types: intersect(content_types, location.content_types),
90
+ accepts: intersect(accepts, location.accepts),
85
91
  controller: location.controller,
86
92
  &block
87
93
  )
94
+ child.options[:nested_locations].each(&:call)
95
+ location.children << child
88
96
  }
89
- if location.parent.nil?
90
- location.options[:middleware_loaders] << build_child
91
- else
92
- build_child[]
93
- end
97
+ location.options[:nested_locations] << build_child
94
98
  end
95
99
 
96
100
  end
@@ -2,17 +2,16 @@ module Itsi
2
2
  class Server
3
3
  module Config
4
4
  class Proxy < Middleware
5
-
6
5
  insert_text <<~SNIPPET
7
- proxy \\
8
- to: "${1:http://backend.example.com/api{path}{query}}", \\
9
- backends: [${2:"127.0.0.1:3001", "127.0.0.1:3002"}], \\
10
- backend_priority: ${3|"round_robin","ordered","random"|}, \\
11
- headers: { ${4| "X-Forwarded-For" => { rewrite: "{addr}" },|} }, \\
12
- verify_ssl: ${5|true,false|}, \\
13
- timeout: ${6|30,60|}, \\
14
- tls_sni: ${7|true,false|}, \\
15
- error_response: ${8|"bad_gateway", "service_unavailable", { code: 503\\, default_format: "html"\\, html: { inline: "<h1>Service Unavailable</h1>" } }|}
6
+ proxy \\
7
+ to: "${1:http://backend.example.com{path_and_query}",
8
+ backends: [${2:"127.0.0.1:3001", "127.0.0.1:3002"}],
9
+ backend_priority: ${3|"round_robin","ordered","random"|},
10
+ headers: { ${4| "X-Forwarded-For" => { rewrite: "{addr}" },|} },
11
+ verify_ssl: ${5|true,false|},
12
+ timeout: ${6|30,60|},
13
+ tls_sni: ${7|true,false|},
14
+ error_response: ${8|"bad_gateway", "service_unavailable", { code: 503\\, default_format: "html"\\, html: { inline: "<h1>Service Unavailable</h1>" } }|}
16
15
  SNIPPET
17
16
 
18
17
  detail "Forwards incoming requests to a backend server using dynamic URL rewriting. Supports various backend selection strategies and header overriding."
@@ -21,18 +20,20 @@ module Itsi
21
20
  {
22
21
  to: Type(String) & Required(),
23
22
  backends: Array(Type(String)),
24
- backend_priority: Enum(["round_robin", "ordered", "random"]).default("round_robin"),
23
+ backend_priority: Enum(%w[round_robin ordered random]).default("round_robin"),
25
24
  headers: Hash(Type(String), Type(String)).default({}),
26
25
  verify_ssl: Bool().default(true),
27
26
  tls_sni: Bool().default(true),
28
27
  timeout: Type(Integer).default(30),
29
- error_response: Type(ErrorResponseDef).default("bad_gateway"),
28
+ error_response: Type(ErrorResponseDef).default("bad_gateway")
30
29
  }
31
30
  end
32
31
 
33
32
  def build!
34
- require 'uri'
35
- @params[:backends]||= URI.extract(@params[:to]).map(&URI.method(:parse)).map{|u| "#{u.scheme}://#{u.host}:#{u.port}" }
33
+ require "uri"
34
+ @params[:backends] ||= URI.extract(@params[:to]).map(&URI.method(:parse)).map do |u|
35
+ "#{u.scheme}://#{u.host}:#{u.port}"
36
+ end
36
37
  super
37
38
  end
38
39
  end
@@ -2,12 +2,11 @@ module Itsi
2
2
  class Server
3
3
  module Config
4
4
  class RackupFile < Middleware
5
-
6
5
  insert_text <<~SNIPPET
7
- rackup_file \\
8
- "config.ru",
9
- nonblocking: ${2|true,false|},
10
- sendfile: ${3|true,false|}
6
+ rackup_file \\
7
+ "config.ru",
8
+ nonblocking: ${2|true,false|},
9
+ sendfile: ${3|true,false|}
11
10
 
12
11
  SNIPPET
13
12
 
@@ -23,20 +22,18 @@ module Itsi
23
22
  def initialize(location, app, **params)
24
23
  super(location, params)
25
24
  raise "Rackup file must be a string" unless app.is_a?(String)
25
+
26
26
  @app = Itsi::Server::RackInterface.for(app)
27
27
  end
28
28
 
29
29
  def build!
30
30
  app_args = {
31
- preloader: -> { @app},
31
+ preloader: -> { @app },
32
32
  sendfile: @params[:sendfile],
33
33
  nonblocking: @params[:nonblocking],
34
- base_path: "^(?<base_path>#{location.paths_from_parent.gsub(/\.\*\)$/, ')')}).*$"
34
+ base_path: "^(?<base_path>#{location.paths_from_parent.gsub(/\.\*\)$/, ")")}).*$"
35
35
  }
36
36
  location.middleware[:app] = app_args
37
- location.location("*") do
38
- @middleware[:app] = app_args
39
- end
40
37
  end
41
38
  end
42
39
  end
@@ -75,11 +75,15 @@ module Itsi
75
75
  @params[:allowed_extensions] << ""
76
76
  end
77
77
 
78
- @params[:base_path] = "^(?<base_path>#{location.paths_from_parent}).*$"
79
- params = @params
80
- location.location("*", extensions: @params[:allowed_extensions]) do
81
- @middleware[:static_assets] = params
78
+ if @params[:allowed_extensions].any? && @params[:auto_index]
79
+ @params[:allowed_extensions] |= ["html"]
80
+ @params[:allowed_extensions] |= [""]
82
81
  end
82
+
83
+ @params[:base_path] = "^(?<base_path>#{location.paths_from_parent.gsub(/\.\*\)$/,")")}).*$"
84
+ params = @params
85
+
86
+ location.middleware[:static_assets] = params
83
87
  end
84
88
  end
85
89
  end
@@ -13,6 +13,20 @@ The String Rewrite mechanism is used when configuring Itsi for
13
13
 
14
14
  It allows you to create dynamic strings from a template by combining literal text with placeholders. Placeholders (denoted using curly braces: `{}`) are replaced at runtime with data derived from the HTTP request, response, or context.
15
15
 
16
+ Modifiers can be appended after a pipe | to transform the substituted value.
17
+
18
+ ## Modifiers
19
+
20
+ After a placeholder name, add |<modifier>:<arg> (or for replace, |replace:<from>,<to>). Available modifiers:
21
+
22
+ `strip_prefix:<text>` If the substituted value starts with <text>, remove that prefix.
23
+
24
+ `strip_suffix:<text>` If the substituted value ends with <text>, remove that suffix.
25
+
26
+ `replace:<from>,<to>` Replace all occurrences of <from> in the substituted value with <to>.
27
+
28
+ Modifiers are applied in the order they appear. You can chain multiple modifiers by repeating the |<modifier>:<arg> syntax (e.g. `{path|strip_prefix:/rails|replace:old,new}`).
29
+
16
30
  ### Rewriting a Request
17
31
 
18
32
  The following placeholders are supported:
@@ -3,7 +3,7 @@ module Itsi
3
3
  module Config
4
4
  class Include < Option
5
5
 
6
- insert_text "include \"${1|other_file.rb|}\" # Include another file to be loaded within the current configuration"
6
+ insert_text "include \"${1|other_file|}\" # Include another file to be loaded within the current configuration"
7
7
 
8
8
  detail "Include another file to be loaded within the current configuration"
9
9
 
@@ -7,7 +7,7 @@ module Itsi
7
7
  if app.is_a?(String)
8
8
  dir = File.expand_path(File.dirname(app))
9
9
  Dir.chdir(dir) do
10
- loaded_app = ::Rack::Builder.parse_file(app)
10
+ loaded_app = ::Rack::Builder.parse_file(File.basename(app))
11
11
  app = loaded_app.is_a?(Array) ? loaded_app.first : loaded_app
12
12
  end
13
13
  end
@@ -4,7 +4,7 @@ module Itsi
4
4
  module RouteTester
5
5
  require "set"
6
6
  require "strscan"
7
- require "debug"
7
+
8
8
  def format_mw(mw)
9
9
  mw_name, mw_args = mw
10
10
  case mw_name
@@ -143,6 +143,31 @@ module Itsi
143
143
  # Fixed keys are converted to symbols, and regex-matched keys remain as strings.
144
144
  # The current location in the params is tracked as an array of path segments.
145
145
  def apply_schema!(params, schema, path = [])
146
+ # Support top-level array schema: homogeneous arrays.
147
+ if schema.is_a?(Array)
148
+ # Only allow homogeneous array types
149
+ unless schema.size == 1
150
+ raise ValidationError.new(["Schema Array must contain exactly one type. Got #{schema.size}"])
151
+ end
152
+ expected_type = schema.first
153
+ # Expect params to be an Array
154
+ unless params.is_a?(Array)
155
+ raise ValidationError.new(["Expected Array at #{format_path(path)}, got #{params.class}"])
156
+ end
157
+ errors = []
158
+ params.each_with_index do |_, idx|
159
+ err = cast_value!(params, idx, expected_type, path + [idx])
160
+ errors << err if err
161
+ end
162
+ raise ValidationError.new(errors) unless errors.empty?
163
+ return params
164
+ end
165
+
166
+ # Ensure schema is a Hash
167
+ unless schema.is_a?(Hash)
168
+ raise ValidationError.new(["Unsupported schema type: #{schema.class} at #{format_path(path)}"])
169
+ end
170
+
146
171
  errors = []
147
172
  processed = processed_schema(schema)
148
173
  fixed_schema = processed[0]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.2.3"
5
+ VERSION = "0.2.4"
6
6
  end
7
7
  end
@@ -262,7 +262,7 @@ module Itsi
262
262
  end
263
263
 
264
264
  def routes(cli_params = {})
265
- load_route_middleware_stack(cli_params).each do |stack|
265
+ load_route_middleware_stack(cli_params).first.each do |stack|
266
266
  routes = explode_route_pattern(stack["route"].source)
267
267
  routes.each do |route|
268
268
  print_route(route, stack)
@@ -133,6 +133,7 @@ class TestStaticAssets < Minitest::Test
133
133
  end
134
134
  end
135
135
  end
136
+
136
137
  def test_relative_path_true
137
138
  Dir.mktmpdir do |dir|
138
139
  # Create file "test.txt" in root_dir.
@@ -141,18 +142,19 @@ class TestStaticAssets < Minitest::Test
141
142
  File.write(file_path, "relative true")
142
143
  server(
143
144
  itsi_rb: lambda do
144
- location "foo/bar/baz" do
145
+ location "foo/bar/baz/*" do
145
146
  static_assets \
146
147
  root_dir: "#{dir}",
147
148
  not_found_behavior: "fallthrough",
148
149
  allowed_extensions: ["txt"],
149
150
  relative_path: true
150
151
 
151
- get("/foo/bar/baz/test.txt") { |r| r.ok "fallback" }
152
+ get("/test.txt") { |r| r.not_found }
152
153
  end
153
154
  end
154
155
  ) do
155
156
  res = get_resp("/foo/bar/baz/test.txt")
157
+
156
158
  assert_equal "200", res.code, "Expected 200 OK with relative_path: true"
157
159
  assert_equal "relative true", res.body
158
160
  end
@@ -169,14 +171,14 @@ class TestStaticAssets < Minitest::Test
169
171
  server(
170
172
  itsi_rb: lambda do
171
173
 
172
- location "/foo/bar/baz/" do
174
+ location "/foo/bar/baz/*" do
173
175
  static_assets \
174
176
  root_dir: "#{dir}",
175
177
  not_found_behavior: "fallthrough",
176
178
  allowed_extensions: ["txt"],
177
179
  relative_path: false
178
180
 
179
- get("/foo/bar/baz/test.txt") { |r| r.ok "fallback" }
181
+ get("/test.txt") { |r| r.ok "fallback" }
180
182
  end
181
183
  end
182
184
  ) do
@@ -109,4 +109,58 @@ class TestStringRewrite < Minitest::Test
109
109
  assert_equal "https://example.com/{unknown}", res["Location"]
110
110
  end
111
111
  end
112
+
113
+ def test_strip_prefix
114
+ server(
115
+ itsi_rb: lambda do
116
+ redirect to: "https://example.com{path|strip_prefix:/rails}", type: "temporary"
117
+ get("/rails/foo") { |r| r.ok }
118
+ end
119
+ ) do
120
+ res = get_resp("/rails/foo")
121
+ assert_equal "307", res.code
122
+ assert_equal "https://example.com/foo", res["Location"]
123
+ end
124
+ end
125
+
126
+ def test_strip_suffix
127
+ server(
128
+ itsi_rb: lambda do
129
+ redirect to: "{path_and_query|strip_suffix:.json}", type: "found"
130
+ get("/data.json") { |r| r.ok }
131
+ end
132
+ ) do
133
+ res = get_resp("/data.json")
134
+ assert_equal "302", res.code
135
+ # .json is removed from end of "/data.json"
136
+ assert_equal "/data", res["Location"]
137
+ end
138
+ end
139
+
140
+ def test_replace
141
+ server(
142
+ itsi_rb: lambda do
143
+ redirect to: "https://{host|replace:localhost,api.example.com}", type: "permanent"
144
+ get("/foo") { |r| r.ok }
145
+ end
146
+ ) do
147
+ res = get_resp("/foo")
148
+ assert_equal "308", res.code
149
+ assert_equal "https://api.example.com", res["Location"]
150
+ end
151
+ end
152
+
153
+ def test_chain_modifiers
154
+ server(
155
+ itsi_rb: lambda do
156
+ redirect to: "https://example.com{path|strip_prefix:/v1|replace:api,service}", type: "temporary"
157
+ get("/v1/api/users") { |r| r.ok }
158
+ end
159
+ ) do
160
+ res = get_resp("/v1/api/users")
161
+ assert_equal "307", res.code
162
+ # after strip_prefix: "/api/users", then replace "api"→"service"
163
+ assert_equal "https://example.com/service/users", res["Location"]
164
+ end
165
+ end
112
166
  end
@@ -18,6 +18,11 @@ class TestLogRequests < Minitest::Test
18
18
  get_resp("/foo?bar=baz")
19
19
  end
20
20
  end
21
+ server(
22
+ itsi_rb: lambda do
23
+ log_level :error
24
+ end
25
+ ){}
21
26
 
22
27
  # should emit something like "[a1b2c3] BEFORE GET /foo?bar=baz"
23
28
  assert_match(%r{\[[0-9a-f]+\] BEFORE GET /foo\?bar=baz}, stdout)
@@ -39,10 +44,17 @@ class TestLogRequests < Minitest::Test
39
44
  ) do
40
45
  get_resp("/foo")
41
46
  end
47
+
48
+ server(
49
+ itsi_rb: lambda do
50
+ log_level :error
51
+ end
52
+ ){}
53
+
42
54
  end
43
55
 
44
56
  # should emit something like "[d4e5f6] AFTER 200 in 1.234ms"
45
- assert_match(/\[[0-9a-f]+\] AFTER 200 in \d+\.\d+ms/, stdout)
57
+ assert_match(/\[[0-9a-f]+\] AFTER 200 in \d+(?:\.\d+)?.?s/, stdout)
46
58
  end
47
59
 
48
60
  # 3. custom log‑level is honored
data/lib/itsi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Itsi
2
- VERSION = '0.2.3'
2
+ VERSION = "0.2.4"
3
3
  end