itsi 0.2.2 → 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 (498) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/Cargo.lock +29 -30
  4. data/README.md +1 -0
  5. data/Rakefile +2 -2
  6. data/crates/itsi_scheduler/Cargo.toml +1 -1
  7. data/crates/itsi_server/Cargo.toml +1 -1
  8. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +26 -3
  9. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +28 -11
  10. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +1 -1
  11. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +1 -2
  12. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +14 -2
  13. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +86 -41
  14. data/crates/itsi_server/src/services/itsi_http_service.rs +46 -35
  15. data/crates/itsi_server/src/services/static_file_server.rs +31 -3
  16. data/docs/content/_index.md +1 -1
  17. data/docs/content/contact/_index.md +1 -1
  18. data/docs/content/directory_listing.jpg +0 -0
  19. data/docs/content/error_page.jpg +0 -0
  20. data/docs/content/getting_started/local_development.md +9 -2
  21. data/docs/content/getting_started/logging.md +1 -2
  22. data/docs/content/getting_started/signals.md +0 -1
  23. data/docs/hugo.yaml +3 -0
  24. data/examples/api_with_schema_and_controllers/Itsi.rb +35 -0
  25. data/examples/api_with_schema_and_controllers/README.md +10 -0
  26. data/examples/api_with_schema_and_controllers/controllers.rb +66 -0
  27. data/examples/api_with_schema_and_controllers/schemas.rb +25 -0
  28. data/examples/file_server/Itsi.rb +30 -0
  29. data/examples/file_server/README.md +3 -0
  30. data/examples/file_server/about.html +80 -0
  31. data/examples/file_server/admin/secrets.txt +1 -0
  32. data/examples/file_server/fairytale.txt +33 -0
  33. data/examples/file_server/itsi-server-100.png +0 -0
  34. data/examples/file_server/subdirectory/movies.csv +21 -0
  35. data/examples/helpers/datastore.rb +84 -0
  36. data/examples/media_server/Itsi.rb +5 -0
  37. data/examples/media_server/README.md +3 -0
  38. data/examples/media_server/images/itsi-server-100.png +0 -0
  39. data/examples/media_server/index.html +82 -0
  40. data/examples/multi_rack_rails_sinatra/Gemfile +66 -0
  41. data/examples/multi_rack_rails_sinatra/Gemfile.lock +416 -0
  42. data/examples/multi_rack_rails_sinatra/Itsi.rb +44 -0
  43. data/examples/multi_rack_rails_sinatra/README.md +7 -0
  44. data/examples/multi_rack_rails_sinatra/index.html +119 -0
  45. data/examples/multi_rack_rails_sinatra/rails_subapp/.dockerignore +51 -0
  46. data/examples/multi_rack_rails_sinatra/rails_subapp/.gitattributes +9 -0
  47. data/examples/multi_rack_rails_sinatra/rails_subapp/.github/dependabot.yml +12 -0
  48. data/examples/multi_rack_rails_sinatra/rails_subapp/.github/workflows/ci.yml +90 -0
  49. data/examples/multi_rack_rails_sinatra/rails_subapp/.gitignore +34 -0
  50. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/docker-setup.sample +3 -0
  51. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/post-app-boot.sample +3 -0
  52. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/post-deploy.sample +14 -0
  53. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/post-proxy-reboot.sample +3 -0
  54. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-app-boot.sample +3 -0
  55. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-build.sample +51 -0
  56. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-connect.sample +47 -0
  57. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-deploy.sample +109 -0
  58. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  59. data/examples/multi_rack_rails_sinatra/rails_subapp/.kamal/secrets +17 -0
  60. data/examples/multi_rack_rails_sinatra/rails_subapp/.rubocop.yml +8 -0
  61. data/examples/multi_rack_rails_sinatra/rails_subapp/.ruby-version +1 -0
  62. data/examples/multi_rack_rails_sinatra/rails_subapp/Dockerfile +72 -0
  63. data/examples/multi_rack_rails_sinatra/rails_subapp/Gemfile +66 -0
  64. data/examples/multi_rack_rails_sinatra/rails_subapp/Gemfile.lock +416 -0
  65. data/examples/multi_rack_rails_sinatra/rails_subapp/README.md +24 -0
  66. data/examples/multi_rack_rails_sinatra/rails_subapp/Rakefile +6 -0
  67. data/examples/multi_rack_rails_sinatra/rails_subapp/app/assets/images/.keep +0 -0
  68. data/examples/multi_rack_rails_sinatra/rails_subapp/app/assets/stylesheets/application.css +161 -0
  69. data/examples/multi_rack_rails_sinatra/rails_subapp/app/controllers/application_controller.rb +4 -0
  70. data/examples/multi_rack_rails_sinatra/rails_subapp/app/controllers/articles_controller.rb +70 -0
  71. data/examples/multi_rack_rails_sinatra/rails_subapp/app/controllers/concerns/.keep +0 -0
  72. data/examples/multi_rack_rails_sinatra/rails_subapp/app/controllers/home_controller.rb +4 -0
  73. data/examples/multi_rack_rails_sinatra/rails_subapp/app/helpers/application_helper.rb +2 -0
  74. data/examples/multi_rack_rails_sinatra/rails_subapp/app/helpers/articles_helper.rb +2 -0
  75. data/examples/multi_rack_rails_sinatra/rails_subapp/app/helpers/home_helper.rb +2 -0
  76. data/examples/multi_rack_rails_sinatra/rails_subapp/app/javascript/application.js +3 -0
  77. data/examples/multi_rack_rails_sinatra/rails_subapp/app/javascript/controllers/application.js +9 -0
  78. data/examples/multi_rack_rails_sinatra/rails_subapp/app/javascript/controllers/hello_controller.js +7 -0
  79. data/examples/multi_rack_rails_sinatra/rails_subapp/app/javascript/controllers/index.js +4 -0
  80. data/examples/multi_rack_rails_sinatra/rails_subapp/app/jobs/application_job.rb +7 -0
  81. data/examples/multi_rack_rails_sinatra/rails_subapp/app/mailers/application_mailer.rb +4 -0
  82. data/examples/multi_rack_rails_sinatra/rails_subapp/app/models/application_record.rb +3 -0
  83. data/examples/multi_rack_rails_sinatra/rails_subapp/app/models/article.rb +2 -0
  84. data/examples/multi_rack_rails_sinatra/rails_subapp/app/models/concerns/.keep +0 -0
  85. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/_article.html.erb +14 -0
  86. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/_article.json.jbuilder +2 -0
  87. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/_form.html.erb +30 -0
  88. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/edit.html.erb +12 -0
  89. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/index.html.erb +29 -0
  90. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/index.json.jbuilder +1 -0
  91. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/new.html.erb +11 -0
  92. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/show.html.erb +10 -0
  93. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/articles/show.json.jbuilder +1 -0
  94. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/home/index.html.erb +8 -0
  95. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/layouts/application.html.erb +34 -0
  96. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/layouts/mailer.html.erb +13 -0
  97. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/layouts/mailer.text.erb +1 -0
  98. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/pwa/manifest.json.erb +22 -0
  99. data/examples/multi_rack_rails_sinatra/rails_subapp/app/views/pwa/service-worker.js +26 -0
  100. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/brakeman +7 -0
  101. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/bundle +109 -0
  102. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/dev +2 -0
  103. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/docker-entrypoint +14 -0
  104. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/importmap +4 -0
  105. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/jobs +6 -0
  106. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/kamal +27 -0
  107. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/rails +4 -0
  108. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/rake +4 -0
  109. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/rubocop +8 -0
  110. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/setup +34 -0
  111. data/examples/multi_rack_rails_sinatra/rails_subapp/bin/thrust +5 -0
  112. data/examples/multi_rack_rails_sinatra/rails_subapp/config/application.rb +27 -0
  113. data/examples/multi_rack_rails_sinatra/rails_subapp/config/boot.rb +4 -0
  114. data/examples/multi_rack_rails_sinatra/rails_subapp/config/cable.yml +17 -0
  115. data/examples/multi_rack_rails_sinatra/rails_subapp/config/cache.yml +16 -0
  116. data/examples/multi_rack_rails_sinatra/rails_subapp/config/credentials.yml.enc +1 -0
  117. data/examples/multi_rack_rails_sinatra/rails_subapp/config/database.yml +41 -0
  118. data/examples/multi_rack_rails_sinatra/rails_subapp/config/deploy.yml +116 -0
  119. data/examples/multi_rack_rails_sinatra/rails_subapp/config/environment.rb +5 -0
  120. data/examples/multi_rack_rails_sinatra/rails_subapp/config/environments/development.rb +72 -0
  121. data/examples/multi_rack_rails_sinatra/rails_subapp/config/environments/production.rb +90 -0
  122. data/examples/multi_rack_rails_sinatra/rails_subapp/config/environments/test.rb +53 -0
  123. data/examples/multi_rack_rails_sinatra/rails_subapp/config/importmap.rb +7 -0
  124. data/examples/multi_rack_rails_sinatra/rails_subapp/config/initializers/assets.rb +7 -0
  125. data/examples/multi_rack_rails_sinatra/rails_subapp/config/initializers/content_security_policy.rb +25 -0
  126. data/examples/multi_rack_rails_sinatra/rails_subapp/config/initializers/filter_parameter_logging.rb +8 -0
  127. data/examples/multi_rack_rails_sinatra/rails_subapp/config/initializers/inflections.rb +16 -0
  128. data/examples/multi_rack_rails_sinatra/rails_subapp/config/locales/en.yml +31 -0
  129. data/examples/multi_rack_rails_sinatra/rails_subapp/config/puma.rb +41 -0
  130. data/examples/multi_rack_rails_sinatra/rails_subapp/config/queue.yml +18 -0
  131. data/examples/multi_rack_rails_sinatra/rails_subapp/config/recurring.yml +10 -0
  132. data/examples/multi_rack_rails_sinatra/rails_subapp/config/routes.rb +17 -0
  133. data/examples/multi_rack_rails_sinatra/rails_subapp/config/storage.yml +34 -0
  134. data/examples/multi_rack_rails_sinatra/rails_subapp/config.ru +6 -0
  135. data/examples/multi_rack_rails_sinatra/rails_subapp/db/cable_schema.rb +11 -0
  136. data/examples/multi_rack_rails_sinatra/rails_subapp/db/cache_schema.rb +14 -0
  137. data/examples/multi_rack_rails_sinatra/rails_subapp/db/migrate/20250422211855_create_articles.rb +10 -0
  138. data/examples/multi_rack_rails_sinatra/rails_subapp/db/queue_schema.rb +129 -0
  139. data/examples/multi_rack_rails_sinatra/rails_subapp/db/schema.rb +20 -0
  140. data/examples/multi_rack_rails_sinatra/rails_subapp/db/seeds.rb +9 -0
  141. data/examples/multi_rack_rails_sinatra/rails_subapp/lib/tasks/.keep +0 -0
  142. data/examples/multi_rack_rails_sinatra/rails_subapp/log/.keep +0 -0
  143. data/examples/multi_rack_rails_sinatra/rails_subapp/public/400.html +114 -0
  144. data/examples/multi_rack_rails_sinatra/rails_subapp/public/404.html +114 -0
  145. data/examples/multi_rack_rails_sinatra/rails_subapp/public/406-unsupported-browser.html +114 -0
  146. data/examples/multi_rack_rails_sinatra/rails_subapp/public/422.html +114 -0
  147. data/examples/multi_rack_rails_sinatra/rails_subapp/public/500.html +114 -0
  148. data/examples/multi_rack_rails_sinatra/rails_subapp/public/icon.png +0 -0
  149. data/examples/multi_rack_rails_sinatra/rails_subapp/public/icon.svg +3 -0
  150. data/examples/multi_rack_rails_sinatra/rails_subapp/public/robots.txt +1 -0
  151. data/examples/multi_rack_rails_sinatra/rails_subapp/script/.keep +0 -0
  152. data/examples/multi_rack_rails_sinatra/rails_subapp/storage/.keep +0 -0
  153. data/examples/multi_rack_rails_sinatra/rails_subapp/test/application_system_test_case.rb +5 -0
  154. data/examples/multi_rack_rails_sinatra/rails_subapp/test/controllers/.keep +0 -0
  155. data/examples/multi_rack_rails_sinatra/rails_subapp/test/controllers/articles_controller_test.rb +48 -0
  156. data/examples/multi_rack_rails_sinatra/rails_subapp/test/controllers/home_controller_test.rb +8 -0
  157. data/examples/multi_rack_rails_sinatra/rails_subapp/test/fixtures/articles.yml +9 -0
  158. data/examples/multi_rack_rails_sinatra/rails_subapp/test/fixtures/files/.keep +0 -0
  159. data/examples/multi_rack_rails_sinatra/rails_subapp/test/helpers/.keep +0 -0
  160. data/examples/multi_rack_rails_sinatra/rails_subapp/test/integration/.keep +0 -0
  161. data/examples/multi_rack_rails_sinatra/rails_subapp/test/mailers/.keep +0 -0
  162. data/examples/multi_rack_rails_sinatra/rails_subapp/test/models/.keep +0 -0
  163. data/examples/multi_rack_rails_sinatra/rails_subapp/test/models/article_test.rb +7 -0
  164. data/examples/multi_rack_rails_sinatra/rails_subapp/test/system/.keep +0 -0
  165. data/examples/multi_rack_rails_sinatra/rails_subapp/test/system/articles_test.rb +43 -0
  166. data/examples/multi_rack_rails_sinatra/rails_subapp/test/test_helper.rb +15 -0
  167. data/examples/multi_rack_rails_sinatra/rails_subapp/tmp/.keep +0 -0
  168. data/examples/multi_rack_rails_sinatra/rails_subapp/tmp/pids/.keep +0 -0
  169. data/examples/multi_rack_rails_sinatra/rails_subapp/tmp/storage/.keep +0 -0
  170. data/examples/multi_rack_rails_sinatra/rails_subapp/vendor/.keep +0 -0
  171. data/examples/multi_rack_rails_sinatra/rails_subapp/vendor/javascript/.keep +0 -0
  172. data/examples/multi_rack_rails_sinatra/sinatra_subapp/config.ru +2 -0
  173. data/examples/multi_rack_rails_sinatra/sinatra_subapp/my_app.rb +96 -0
  174. data/examples/rails_with_static_assets/.dockerignore +51 -0
  175. data/examples/rails_with_static_assets/.gitattributes +9 -0
  176. data/examples/rails_with_static_assets/.github/dependabot.yml +12 -0
  177. data/examples/rails_with_static_assets/.github/workflows/ci.yml +90 -0
  178. data/examples/rails_with_static_assets/.gitignore +34 -0
  179. data/examples/rails_with_static_assets/.kamal/hooks/docker-setup.sample +3 -0
  180. data/examples/rails_with_static_assets/.kamal/hooks/post-app-boot.sample +3 -0
  181. data/examples/rails_with_static_assets/.kamal/hooks/post-deploy.sample +14 -0
  182. data/examples/rails_with_static_assets/.kamal/hooks/post-proxy-reboot.sample +3 -0
  183. data/examples/rails_with_static_assets/.kamal/hooks/pre-app-boot.sample +3 -0
  184. data/examples/rails_with_static_assets/.kamal/hooks/pre-build.sample +51 -0
  185. data/examples/rails_with_static_assets/.kamal/hooks/pre-connect.sample +47 -0
  186. data/examples/rails_with_static_assets/.kamal/hooks/pre-deploy.sample +109 -0
  187. data/examples/rails_with_static_assets/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  188. data/examples/rails_with_static_assets/.kamal/secrets +17 -0
  189. data/examples/rails_with_static_assets/.rubocop.yml +8 -0
  190. data/examples/rails_with_static_assets/.ruby-version +1 -0
  191. data/examples/rails_with_static_assets/Dockerfile +72 -0
  192. data/examples/rails_with_static_assets/Gemfile +66 -0
  193. data/examples/rails_with_static_assets/Gemfile.lock +416 -0
  194. data/examples/rails_with_static_assets/Itsi.rb +10 -0
  195. data/examples/rails_with_static_assets/README.md +14 -0
  196. data/examples/rails_with_static_assets/Rakefile +6 -0
  197. data/examples/rails_with_static_assets/app/assets/images/.keep +0 -0
  198. data/examples/rails_with_static_assets/app/assets/stylesheets/application.css +161 -0
  199. data/examples/rails_with_static_assets/app/controllers/application_controller.rb +4 -0
  200. data/examples/rails_with_static_assets/app/controllers/articles_controller.rb +70 -0
  201. data/examples/rails_with_static_assets/app/controllers/concerns/.keep +0 -0
  202. data/examples/rails_with_static_assets/app/controllers/home_controller.rb +4 -0
  203. data/examples/rails_with_static_assets/app/helpers/application_helper.rb +2 -0
  204. data/examples/rails_with_static_assets/app/helpers/articles_helper.rb +2 -0
  205. data/examples/rails_with_static_assets/app/helpers/home_helper.rb +2 -0
  206. data/examples/rails_with_static_assets/app/javascript/application.js +3 -0
  207. data/examples/rails_with_static_assets/app/javascript/controllers/application.js +9 -0
  208. data/examples/rails_with_static_assets/app/javascript/controllers/hello_controller.js +7 -0
  209. data/examples/rails_with_static_assets/app/javascript/controllers/index.js +4 -0
  210. data/examples/rails_with_static_assets/app/jobs/application_job.rb +7 -0
  211. data/examples/rails_with_static_assets/app/mailers/application_mailer.rb +4 -0
  212. data/examples/rails_with_static_assets/app/models/application_record.rb +3 -0
  213. data/examples/rails_with_static_assets/app/models/article.rb +2 -0
  214. data/examples/rails_with_static_assets/app/models/concerns/.keep +0 -0
  215. data/examples/rails_with_static_assets/app/views/articles/_article.html.erb +14 -0
  216. data/examples/rails_with_static_assets/app/views/articles/_article.json.jbuilder +2 -0
  217. data/examples/rails_with_static_assets/app/views/articles/_form.html.erb +30 -0
  218. data/examples/rails_with_static_assets/app/views/articles/edit.html.erb +12 -0
  219. data/examples/rails_with_static_assets/app/views/articles/index.html.erb +29 -0
  220. data/examples/rails_with_static_assets/app/views/articles/index.json.jbuilder +1 -0
  221. data/examples/rails_with_static_assets/app/views/articles/new.html.erb +11 -0
  222. data/examples/rails_with_static_assets/app/views/articles/show.html.erb +10 -0
  223. data/examples/rails_with_static_assets/app/views/articles/show.json.jbuilder +1 -0
  224. data/examples/rails_with_static_assets/app/views/home/index.html.erb +8 -0
  225. data/examples/rails_with_static_assets/app/views/layouts/application.html.erb +34 -0
  226. data/examples/rails_with_static_assets/app/views/layouts/mailer.html.erb +13 -0
  227. data/examples/rails_with_static_assets/app/views/layouts/mailer.text.erb +1 -0
  228. data/examples/rails_with_static_assets/app/views/pwa/manifest.json.erb +22 -0
  229. data/examples/rails_with_static_assets/app/views/pwa/service-worker.js +26 -0
  230. data/examples/rails_with_static_assets/bin/brakeman +7 -0
  231. data/examples/rails_with_static_assets/bin/bundle +109 -0
  232. data/examples/rails_with_static_assets/bin/dev +2 -0
  233. data/examples/rails_with_static_assets/bin/docker-entrypoint +14 -0
  234. data/examples/rails_with_static_assets/bin/importmap +4 -0
  235. data/examples/rails_with_static_assets/bin/jobs +6 -0
  236. data/examples/rails_with_static_assets/bin/kamal +27 -0
  237. data/examples/rails_with_static_assets/bin/rails +4 -0
  238. data/examples/rails_with_static_assets/bin/rake +4 -0
  239. data/examples/rails_with_static_assets/bin/rubocop +8 -0
  240. data/examples/rails_with_static_assets/bin/setup +34 -0
  241. data/examples/rails_with_static_assets/bin/thrust +5 -0
  242. data/examples/rails_with_static_assets/config/application.rb +26 -0
  243. data/examples/rails_with_static_assets/config/boot.rb +4 -0
  244. data/examples/rails_with_static_assets/config/cable.yml +17 -0
  245. data/examples/rails_with_static_assets/config/cache.yml +16 -0
  246. data/examples/rails_with_static_assets/config/credentials.yml.enc +1 -0
  247. data/examples/rails_with_static_assets/config/database.yml +41 -0
  248. data/examples/rails_with_static_assets/config/deploy.yml +116 -0
  249. data/examples/rails_with_static_assets/config/environment.rb +5 -0
  250. data/examples/rails_with_static_assets/config/environments/development.rb +74 -0
  251. data/examples/rails_with_static_assets/config/environments/production.rb +90 -0
  252. data/examples/rails_with_static_assets/config/environments/test.rb +53 -0
  253. data/examples/rails_with_static_assets/config/importmap.rb +7 -0
  254. data/examples/rails_with_static_assets/config/initializers/assets.rb +7 -0
  255. data/examples/rails_with_static_assets/config/initializers/content_security_policy.rb +25 -0
  256. data/examples/rails_with_static_assets/config/initializers/filter_parameter_logging.rb +8 -0
  257. data/examples/rails_with_static_assets/config/initializers/inflections.rb +16 -0
  258. data/examples/rails_with_static_assets/config/locales/en.yml +31 -0
  259. data/examples/rails_with_static_assets/config/puma.rb +41 -0
  260. data/examples/rails_with_static_assets/config/queue.yml +18 -0
  261. data/examples/rails_with_static_assets/config/recurring.yml +10 -0
  262. data/examples/rails_with_static_assets/config/routes.rb +17 -0
  263. data/examples/rails_with_static_assets/config/storage.yml +34 -0
  264. data/examples/rails_with_static_assets/config.ru +6 -0
  265. data/examples/rails_with_static_assets/db/cable_schema.rb +11 -0
  266. data/examples/rails_with_static_assets/db/cache_schema.rb +14 -0
  267. data/examples/rails_with_static_assets/db/migrate/20250422211855_create_articles.rb +10 -0
  268. data/examples/rails_with_static_assets/db/queue_schema.rb +129 -0
  269. data/examples/rails_with_static_assets/db/schema.rb +20 -0
  270. data/examples/rails_with_static_assets/db/seeds.rb +9 -0
  271. data/examples/rails_with_static_assets/lib/tasks/.keep +0 -0
  272. data/examples/rails_with_static_assets/log/.keep +0 -0
  273. data/examples/rails_with_static_assets/public/400.html +114 -0
  274. data/examples/rails_with_static_assets/public/404.html +114 -0
  275. data/examples/rails_with_static_assets/public/406-unsupported-browser.html +114 -0
  276. data/examples/rails_with_static_assets/public/422.html +114 -0
  277. data/examples/rails_with_static_assets/public/500.html +114 -0
  278. data/examples/rails_with_static_assets/public/icon.png +0 -0
  279. data/examples/rails_with_static_assets/public/icon.svg +3 -0
  280. data/examples/rails_with_static_assets/public/robots.txt +1 -0
  281. data/examples/rails_with_static_assets/script/.keep +0 -0
  282. data/examples/rails_with_static_assets/storage/.keep +0 -0
  283. data/examples/rails_with_static_assets/test/application_system_test_case.rb +5 -0
  284. data/examples/rails_with_static_assets/test/controllers/.keep +0 -0
  285. data/examples/rails_with_static_assets/test/controllers/articles_controller_test.rb +48 -0
  286. data/examples/rails_with_static_assets/test/controllers/home_controller_test.rb +8 -0
  287. data/examples/rails_with_static_assets/test/fixtures/articles.yml +9 -0
  288. data/examples/rails_with_static_assets/test/fixtures/files/.keep +0 -0
  289. data/examples/rails_with_static_assets/test/helpers/.keep +0 -0
  290. data/examples/rails_with_static_assets/test/integration/.keep +0 -0
  291. data/examples/rails_with_static_assets/test/mailers/.keep +0 -0
  292. data/examples/rails_with_static_assets/test/models/.keep +0 -0
  293. data/examples/rails_with_static_assets/test/models/article_test.rb +7 -0
  294. data/examples/rails_with_static_assets/test/system/.keep +0 -0
  295. data/examples/rails_with_static_assets/test/system/articles_test.rb +43 -0
  296. data/examples/rails_with_static_assets/test/test_helper.rb +15 -0
  297. data/examples/rails_with_static_assets/tmp/.keep +0 -0
  298. data/examples/rails_with_static_assets/tmp/pids/.keep +0 -0
  299. data/examples/rails_with_static_assets/tmp/storage/.keep +0 -0
  300. data/examples/rails_with_static_assets/vendor/.keep +0 -0
  301. data/examples/rails_with_static_assets/vendor/javascript/.keep +0 -0
  302. data/examples/reverse_proxy/Itsi.rb +28 -0
  303. data/examples/reverse_proxy/README.md +14 -0
  304. data/examples/reverse_proxy/index.html +119 -0
  305. data/examples/reverse_proxy/rails_subapp/.dockerignore +51 -0
  306. data/examples/reverse_proxy/rails_subapp/.gitattributes +9 -0
  307. data/examples/reverse_proxy/rails_subapp/.github/dependabot.yml +12 -0
  308. data/examples/reverse_proxy/rails_subapp/.github/workflows/ci.yml +90 -0
  309. data/examples/reverse_proxy/rails_subapp/.gitignore +34 -0
  310. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/docker-setup.sample +3 -0
  311. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/post-app-boot.sample +3 -0
  312. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/post-deploy.sample +14 -0
  313. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/post-proxy-reboot.sample +3 -0
  314. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-app-boot.sample +3 -0
  315. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-build.sample +51 -0
  316. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-connect.sample +47 -0
  317. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-deploy.sample +109 -0
  318. data/examples/reverse_proxy/rails_subapp/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  319. data/examples/reverse_proxy/rails_subapp/.kamal/secrets +17 -0
  320. data/examples/reverse_proxy/rails_subapp/.rubocop.yml +8 -0
  321. data/examples/reverse_proxy/rails_subapp/.ruby-version +1 -0
  322. data/examples/reverse_proxy/rails_subapp/Dockerfile +72 -0
  323. data/examples/reverse_proxy/rails_subapp/Gemfile +66 -0
  324. data/examples/reverse_proxy/rails_subapp/Gemfile.lock +416 -0
  325. data/examples/reverse_proxy/rails_subapp/README.md +24 -0
  326. data/examples/reverse_proxy/rails_subapp/Rakefile +6 -0
  327. data/examples/reverse_proxy/rails_subapp/app/assets/images/.keep +0 -0
  328. data/examples/reverse_proxy/rails_subapp/app/assets/stylesheets/application.css +161 -0
  329. data/examples/reverse_proxy/rails_subapp/app/controllers/application_controller.rb +4 -0
  330. data/examples/reverse_proxy/rails_subapp/app/controllers/articles_controller.rb +70 -0
  331. data/examples/reverse_proxy/rails_subapp/app/controllers/concerns/.keep +0 -0
  332. data/examples/reverse_proxy/rails_subapp/app/controllers/home_controller.rb +4 -0
  333. data/examples/reverse_proxy/rails_subapp/app/helpers/application_helper.rb +2 -0
  334. data/examples/reverse_proxy/rails_subapp/app/helpers/articles_helper.rb +2 -0
  335. data/examples/reverse_proxy/rails_subapp/app/helpers/home_helper.rb +2 -0
  336. data/examples/reverse_proxy/rails_subapp/app/javascript/application.js +3 -0
  337. data/examples/reverse_proxy/rails_subapp/app/javascript/controllers/application.js +9 -0
  338. data/examples/reverse_proxy/rails_subapp/app/javascript/controllers/hello_controller.js +7 -0
  339. data/examples/reverse_proxy/rails_subapp/app/javascript/controllers/index.js +4 -0
  340. data/examples/reverse_proxy/rails_subapp/app/jobs/application_job.rb +7 -0
  341. data/examples/reverse_proxy/rails_subapp/app/mailers/application_mailer.rb +4 -0
  342. data/examples/reverse_proxy/rails_subapp/app/models/application_record.rb +3 -0
  343. data/examples/reverse_proxy/rails_subapp/app/models/article.rb +2 -0
  344. data/examples/reverse_proxy/rails_subapp/app/models/concerns/.keep +0 -0
  345. data/examples/reverse_proxy/rails_subapp/app/views/articles/_article.html.erb +14 -0
  346. data/examples/reverse_proxy/rails_subapp/app/views/articles/_article.json.jbuilder +2 -0
  347. data/examples/reverse_proxy/rails_subapp/app/views/articles/_form.html.erb +30 -0
  348. data/examples/reverse_proxy/rails_subapp/app/views/articles/edit.html.erb +12 -0
  349. data/examples/reverse_proxy/rails_subapp/app/views/articles/index.html.erb +29 -0
  350. data/examples/reverse_proxy/rails_subapp/app/views/articles/index.json.jbuilder +1 -0
  351. data/examples/reverse_proxy/rails_subapp/app/views/articles/new.html.erb +11 -0
  352. data/examples/reverse_proxy/rails_subapp/app/views/articles/show.html.erb +10 -0
  353. data/examples/reverse_proxy/rails_subapp/app/views/articles/show.json.jbuilder +1 -0
  354. data/examples/reverse_proxy/rails_subapp/app/views/home/index.html.erb +8 -0
  355. data/examples/reverse_proxy/rails_subapp/app/views/layouts/application.html.erb +34 -0
  356. data/examples/reverse_proxy/rails_subapp/app/views/layouts/mailer.html.erb +13 -0
  357. data/examples/reverse_proxy/rails_subapp/app/views/layouts/mailer.text.erb +1 -0
  358. data/examples/reverse_proxy/rails_subapp/app/views/pwa/manifest.json.erb +22 -0
  359. data/examples/reverse_proxy/rails_subapp/app/views/pwa/service-worker.js +26 -0
  360. data/examples/reverse_proxy/rails_subapp/bin/brakeman +7 -0
  361. data/examples/reverse_proxy/rails_subapp/bin/bundle +109 -0
  362. data/examples/reverse_proxy/rails_subapp/bin/dev +2 -0
  363. data/examples/reverse_proxy/rails_subapp/bin/docker-entrypoint +14 -0
  364. data/examples/reverse_proxy/rails_subapp/bin/importmap +4 -0
  365. data/examples/reverse_proxy/rails_subapp/bin/jobs +6 -0
  366. data/examples/reverse_proxy/rails_subapp/bin/kamal +27 -0
  367. data/examples/reverse_proxy/rails_subapp/bin/rails +4 -0
  368. data/examples/reverse_proxy/rails_subapp/bin/rake +4 -0
  369. data/examples/reverse_proxy/rails_subapp/bin/rubocop +8 -0
  370. data/examples/reverse_proxy/rails_subapp/bin/setup +34 -0
  371. data/examples/reverse_proxy/rails_subapp/bin/thrust +5 -0
  372. data/examples/reverse_proxy/rails_subapp/config/application.rb +26 -0
  373. data/examples/reverse_proxy/rails_subapp/config/boot.rb +4 -0
  374. data/examples/reverse_proxy/rails_subapp/config/cable.yml +17 -0
  375. data/examples/reverse_proxy/rails_subapp/config/cache.yml +16 -0
  376. data/examples/reverse_proxy/rails_subapp/config/credentials.yml.enc +1 -0
  377. data/examples/reverse_proxy/rails_subapp/config/database.yml +41 -0
  378. data/examples/reverse_proxy/rails_subapp/config/deploy.yml +116 -0
  379. data/examples/reverse_proxy/rails_subapp/config/environment.rb +5 -0
  380. data/examples/reverse_proxy/rails_subapp/config/environments/development.rb +72 -0
  381. data/examples/reverse_proxy/rails_subapp/config/environments/production.rb +90 -0
  382. data/examples/reverse_proxy/rails_subapp/config/environments/test.rb +53 -0
  383. data/examples/reverse_proxy/rails_subapp/config/importmap.rb +7 -0
  384. data/examples/reverse_proxy/rails_subapp/config/initializers/assets.rb +7 -0
  385. data/examples/reverse_proxy/rails_subapp/config/initializers/content_security_policy.rb +25 -0
  386. data/examples/reverse_proxy/rails_subapp/config/initializers/filter_parameter_logging.rb +8 -0
  387. data/examples/reverse_proxy/rails_subapp/config/initializers/inflections.rb +16 -0
  388. data/examples/reverse_proxy/rails_subapp/config/locales/en.yml +31 -0
  389. data/examples/reverse_proxy/rails_subapp/config/puma.rb +41 -0
  390. data/examples/reverse_proxy/rails_subapp/config/queue.yml +18 -0
  391. data/examples/reverse_proxy/rails_subapp/config/recurring.yml +10 -0
  392. data/examples/reverse_proxy/rails_subapp/config/routes.rb +17 -0
  393. data/examples/reverse_proxy/rails_subapp/config/storage.yml +34 -0
  394. data/examples/reverse_proxy/rails_subapp/config.ru +6 -0
  395. data/examples/reverse_proxy/rails_subapp/db/cable_schema.rb +11 -0
  396. data/examples/reverse_proxy/rails_subapp/db/cache_schema.rb +14 -0
  397. data/examples/reverse_proxy/rails_subapp/db/migrate/20250422211855_create_articles.rb +10 -0
  398. data/examples/reverse_proxy/rails_subapp/db/queue_schema.rb +129 -0
  399. data/examples/reverse_proxy/rails_subapp/db/schema.rb +20 -0
  400. data/examples/reverse_proxy/rails_subapp/db/seeds.rb +9 -0
  401. data/examples/reverse_proxy/rails_subapp/lib/tasks/.keep +0 -0
  402. data/examples/reverse_proxy/rails_subapp/log/.keep +0 -0
  403. data/examples/reverse_proxy/rails_subapp/public/400.html +114 -0
  404. data/examples/reverse_proxy/rails_subapp/public/404.html +114 -0
  405. data/examples/reverse_proxy/rails_subapp/public/406-unsupported-browser.html +114 -0
  406. data/examples/reverse_proxy/rails_subapp/public/422.html +114 -0
  407. data/examples/reverse_proxy/rails_subapp/public/500.html +114 -0
  408. data/examples/reverse_proxy/rails_subapp/public/icon.png +0 -0
  409. data/examples/reverse_proxy/rails_subapp/public/icon.svg +3 -0
  410. data/examples/reverse_proxy/rails_subapp/public/robots.txt +1 -0
  411. data/examples/reverse_proxy/rails_subapp/script/.keep +0 -0
  412. data/examples/reverse_proxy/rails_subapp/storage/.keep +0 -0
  413. data/examples/reverse_proxy/rails_subapp/test/application_system_test_case.rb +5 -0
  414. data/examples/reverse_proxy/rails_subapp/test/controllers/.keep +0 -0
  415. data/examples/reverse_proxy/rails_subapp/test/controllers/articles_controller_test.rb +48 -0
  416. data/examples/reverse_proxy/rails_subapp/test/controllers/home_controller_test.rb +8 -0
  417. data/examples/reverse_proxy/rails_subapp/test/fixtures/articles.yml +9 -0
  418. data/examples/reverse_proxy/rails_subapp/test/fixtures/files/.keep +0 -0
  419. data/examples/reverse_proxy/rails_subapp/test/helpers/.keep +0 -0
  420. data/examples/reverse_proxy/rails_subapp/test/integration/.keep +0 -0
  421. data/examples/reverse_proxy/rails_subapp/test/mailers/.keep +0 -0
  422. data/examples/reverse_proxy/rails_subapp/test/models/.keep +0 -0
  423. data/examples/reverse_proxy/rails_subapp/test/models/article_test.rb +7 -0
  424. data/examples/reverse_proxy/rails_subapp/test/system/.keep +0 -0
  425. data/examples/reverse_proxy/rails_subapp/test/system/articles_test.rb +43 -0
  426. data/examples/reverse_proxy/rails_subapp/test/test_helper.rb +15 -0
  427. data/examples/reverse_proxy/rails_subapp/tmp/.keep +0 -0
  428. data/examples/reverse_proxy/rails_subapp/tmp/pids/.keep +0 -0
  429. data/examples/reverse_proxy/rails_subapp/tmp/storage/.keep +0 -0
  430. data/examples/reverse_proxy/rails_subapp/vendor/.keep +0 -0
  431. data/examples/reverse_proxy/rails_subapp/vendor/javascript/.keep +0 -0
  432. data/examples/reverse_proxy/sinatra_subapp/Gemfile +2 -0
  433. data/examples/reverse_proxy/sinatra_subapp/Gemfile.lock +46 -0
  434. data/examples/reverse_proxy/sinatra_subapp/config.ru +2 -0
  435. data/examples/reverse_proxy/sinatra_subapp/my_app.rb +96 -0
  436. data/examples/simple_api/Itsi.rb +127 -0
  437. data/examples/simple_api_no_schema/Itsi.rb +86 -0
  438. data/examples/spa/Itsi.rb +5 -0
  439. data/examples/spa/README.md +3 -0
  440. data/examples/spa/dist/assets/index-2cf7be43.js +40 -0
  441. data/examples/spa/dist/index.html +13 -0
  442. data/examples/spa/index.html +12 -0
  443. data/examples/spa/package-lock.json +1265 -0
  444. data/examples/spa/package.json +18 -0
  445. data/examples/spa/src/App.jsx +49 -0
  446. data/examples/spa/src/main.jsx +6 -0
  447. data/examples/spa/src/pages/About.jsx +9 -0
  448. data/examples/spa/src/pages/Home.jsx +9 -0
  449. data/examples/spa/vite.config.js +9 -0
  450. data/gems/scheduler/Cargo.lock +74 -17
  451. data/gems/scheduler/itsi-scheduler.gemspec +2 -2
  452. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  453. data/gems/server/Cargo.lock +28 -29
  454. data/gems/server/itsi-server.gemspec +2 -2
  455. data/gems/server/lib/itsi/http_request.rb +31 -34
  456. data/gems/server/lib/itsi/http_response.rb +10 -8
  457. data/gems/server/lib/itsi/passfile.rb +6 -6
  458. data/gems/server/lib/itsi/server/config/config_helpers.rb +33 -33
  459. data/gems/server/lib/itsi/server/config/dsl.rb +16 -21
  460. data/gems/server/lib/itsi/server/config/known_paths.rb +11 -7
  461. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +0 -4
  462. data/gems/server/lib/itsi/server/config/middleware/error_response.md +13 -0
  463. data/gems/server/lib/itsi/server/config/middleware/location.rb +25 -21
  464. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +15 -14
  465. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +7 -10
  466. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +40 -0
  467. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +8 -4
  468. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +14 -0
  469. data/gems/server/lib/itsi/server/config/option.rb +0 -1
  470. data/gems/server/lib/itsi/server/config/options/include.rb +1 -1
  471. data/gems/server/lib/itsi/server/config/options/nodelay.md +2 -2
  472. data/gems/server/lib/itsi/server/config/options/reuse_address.md +1 -1
  473. data/gems/server/lib/itsi/server/config/typed_struct.rb +32 -35
  474. data/gems/server/lib/itsi/server/config.rb +107 -92
  475. data/gems/server/lib/itsi/server/default_app/default_app.rb +1 -1
  476. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +4 -5
  477. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +6 -7
  478. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +0 -1
  479. data/gems/server/lib/itsi/server/rack_interface.rb +1 -2
  480. data/gems/server/lib/itsi/server/route_tester.rb +26 -24
  481. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +25 -0
  482. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +9 -7
  483. data/gems/server/lib/itsi/server/version.rb +1 -1
  484. data/gems/server/lib/itsi/server.rb +22 -22
  485. data/gems/server/lib/itsi/standard_headers.rb +80 -80
  486. data/gems/server/test/helpers/test_helper.rb +17 -16
  487. data/gems/server/test/middleware/static_assets.rb +6 -4
  488. data/gems/server/test/middleware/string_rewrite.rb +54 -0
  489. data/gems/server/test/middleware/test_log_requests.rb +66 -2
  490. data/gems/server/test/options/test_workers.rb +12 -5
  491. data/lib/itsi/version.rb +1 -1
  492. metadata +435 -13
  493. data/examples/static_assets_example.rb +0 -83
  494. data/grpc_test/Itsi.rb +0 -11
  495. data/grpc_test/echo.proto +0 -14
  496. data/grpc_test/echo_pb.rb +0 -16
  497. data/grpc_test/echo_service_impl.rb +0 -8
  498. data/grpc_test/echo_services_pb.rb +0 -22
@@ -1,14 +1,15 @@
1
1
  use crate::default_responses::{NOT_FOUND_RESPONSE, TIMEOUT_RESPONSE};
2
2
  use crate::ruby_types::itsi_server::itsi_server_config::{ItsiServerTokenPreference, ServerParams};
3
3
  use crate::server::binds::listener::ListenerInfo;
4
- use crate::server::http_message_types::{ConversionExt, HttpResponse, RequestExt, ResponseFormat};
4
+ use crate::server::http_message_types::{
5
+ ConversionExt, HttpRequest, HttpResponse, RequestExt, ResponseFormat,
6
+ };
5
7
  use crate::server::lifecycle_event::LifecycleEvent;
6
8
  use crate::server::middleware_stack::MiddlewareLayer;
7
9
  use crate::server::request_job::RequestJob;
8
10
  use crate::server::serve_strategy::single_mode::RunningPhase;
9
11
  use crate::server::signal::send_lifecycle_event;
10
- use chrono;
11
- use chrono::Local;
12
+ use chrono::{self, DateTime, Local};
12
13
  use either::Either;
13
14
  use http::header::ACCEPT_ENCODING;
14
15
  use http::{HeaderValue, Request};
@@ -18,6 +19,7 @@ use itsi_error::ItsiError;
18
19
  use regex::Regex;
19
20
  use std::sync::atomic::{AtomicBool, Ordering};
20
21
  use std::sync::OnceLock;
22
+ use std::time::{Duration, Instant};
21
23
  use tracing::error;
22
24
 
23
25
  use std::{future::Future, ops::Deref, pin::Pin, sync::Arc};
@@ -68,17 +70,16 @@ impl Deref for RequestContextInner {
68
70
  }
69
71
 
70
72
  pub struct RequestContextInner {
71
- pub request_id: u128,
73
+ pub request_id: u64,
72
74
  pub service: ItsiHttpService,
73
75
  pub accept: ResponseFormat,
74
76
  pub matching_pattern: Option<Arc<Regex>>,
75
77
  pub origin: OnceLock<Option<String>>,
76
78
  pub response_format: OnceLock<ResponseFormat>,
77
- pub start_time: chrono::DateTime<chrono::Utc>,
78
- pub request: Option<Arc<Request<Incoming>>>,
79
- pub request_start_time: OnceLock<chrono::DateTime<Local>>,
79
+ pub request_start_time: OnceLock<DateTime<Local>>,
80
+ pub start_instant: Instant,
80
81
  pub if_none_match: OnceLock<Option<String>>,
81
- pub supported_encoding_set: Vec<HeaderValue>,
82
+ pub supported_encoding_set: OnceLock<Vec<HeaderValue>>,
82
83
  pub is_ruby_request: Arc<AtomicBool>,
83
84
  }
84
85
 
@@ -87,27 +88,38 @@ impl HttpRequestContext {
87
88
  service: ItsiHttpService,
88
89
  matching_pattern: Option<Arc<Regex>>,
89
90
  accept: ResponseFormat,
90
- supported_encoding_set: Vec<HeaderValue>,
91
91
  is_ruby_request: Arc<AtomicBool>,
92
92
  ) -> Self {
93
93
  HttpRequestContext {
94
94
  inner: Arc::new(RequestContextInner {
95
- request_id: rand::random::<u128>(),
95
+ request_id: rand::random::<u64>(),
96
96
  service,
97
97
  matching_pattern,
98
98
  accept,
99
99
  origin: OnceLock::new(),
100
100
  response_format: OnceLock::new(),
101
- start_time: chrono::Utc::now(),
102
- request: None,
103
101
  request_start_time: OnceLock::new(),
102
+ start_instant: Instant::now(),
104
103
  if_none_match: OnceLock::new(),
105
- supported_encoding_set,
104
+ supported_encoding_set: OnceLock::new(),
106
105
  is_ruby_request,
107
106
  }),
108
107
  }
109
108
  }
110
109
 
110
+ pub fn set_supported_encoding_set(&self, req: &HttpRequest) {
111
+ let supported_encoding_set = req
112
+ .headers()
113
+ .get_all(ACCEPT_ENCODING)
114
+ .into_iter()
115
+ .cloned()
116
+ .collect::<Vec<_>>();
117
+ self.inner
118
+ .supported_encoding_set
119
+ .set(supported_encoding_set)
120
+ .unwrap();
121
+ }
122
+
111
123
  pub fn set_origin(&self, origin: Option<String>) {
112
124
  self.inner.origin.set(origin).unwrap();
113
125
  }
@@ -121,28 +133,29 @@ impl HttpRequestContext {
121
133
  }
122
134
 
123
135
  pub fn short_request_id(&self) -> String {
124
- format!("{:016x}", self.inner.request_id & 0xffff_ffff_ffff_ffff)
136
+ format!("{:08x}", self.inner.request_id & 0xffff_ffff)
125
137
  }
126
138
 
127
139
  pub fn request_id(&self) -> String {
128
- format!("{:016x}", self.inner.request_id)
140
+ format!("{:08x}", self.inner.request_id)
129
141
  }
130
142
 
131
- pub fn track_start_time(&self) {
143
+ pub fn init_logging_params(&self) {
132
144
  self.inner
133
145
  .request_start_time
134
146
  .get_or_init(chrono::Local::now);
135
147
  }
136
148
 
137
- pub fn start_time(&self) -> Option<chrono::DateTime<Local>> {
149
+ pub fn start_instant(&self) -> Instant {
150
+ self.inner.start_instant
151
+ }
152
+
153
+ pub fn start_time(&self) -> Option<DateTime<Local>> {
138
154
  self.inner.request_start_time.get().cloned()
139
155
  }
140
156
 
141
- pub fn get_response_time(&self) -> Option<chrono::TimeDelta> {
142
- self.inner
143
- .request_start_time
144
- .get()
145
- .map(|instant| Local::now() - instant)
157
+ pub fn get_response_time(&self) -> Duration {
158
+ self.inner.start_instant.elapsed()
146
159
  }
147
160
 
148
161
  pub fn set_response_format(&self, format: ResponseFormat) {
@@ -152,6 +165,10 @@ impl HttpRequestContext {
152
165
  pub fn response_format(&self) -> &ResponseFormat {
153
166
  self.inner.response_format.get().unwrap()
154
167
  }
168
+
169
+ pub fn supported_encoding_set(&self) -> Option<&Vec<HeaderValue>> {
170
+ self.inner.supported_encoding_set.get()
171
+ }
155
172
  }
156
173
 
157
174
  const SERVER_TOKEN_VERSION: HeaderValue =
@@ -170,12 +187,7 @@ impl Service<Request<Incoming>> for ItsiHttpService {
170
187
  let accept: ResponseFormat = req.accept().into();
171
188
  let accept_clone = accept.clone();
172
189
  let is_single_mode = self.server_params.workers == 1;
173
- let supported_encoding_set = req
174
- .headers()
175
- .get_all(ACCEPT_ENCODING)
176
- .into_iter()
177
- .cloned()
178
- .collect::<Vec<_>>();
190
+
179
191
  let request_timeout = self.server_params.request_timeout;
180
192
  let is_ruby_request = Arc::new(AtomicBool::new(false));
181
193
  let irr_clone = is_ruby_request.clone();
@@ -187,7 +199,6 @@ impl Service<Request<Incoming>> for ItsiHttpService {
187
199
  self_clone,
188
200
  matching_pattern,
189
201
  accept_clone.clone(),
190
- supported_encoding_set,
191
202
  irr_clone,
192
203
  );
193
204
  let mut depth = 0;
@@ -229,8 +240,8 @@ impl Service<Request<Incoming>> for ItsiHttpService {
229
240
  Ok(resp)
230
241
  };
231
242
 
232
- Box::pin(async move {
233
- if let Some(timeout_duration) = request_timeout {
243
+ if let Some(timeout_duration) = request_timeout {
244
+ Box::pin(async move {
234
245
  match timeout(timeout_duration, service_future).await {
235
246
  Ok(result) => result,
236
247
  Err(_) => {
@@ -249,9 +260,9 @@ impl Service<Request<Incoming>> for ItsiHttpService {
249
260
  Ok(TIMEOUT_RESPONSE.to_http_response(accept).await)
250
261
  }
251
262
  }
252
- } else {
253
- service_future.await
254
- }
255
- })
263
+ })
264
+ } else {
265
+ Box::pin(service_future)
266
+ }
256
267
  }
257
268
  }
@@ -51,6 +51,10 @@ pub static ROOT_STATIC_FILE_SERVER: LazyLock<StaticFileServer> = LazyLock::new(|
51
51
  not_found_behavior: NotFoundBehavior::Error(ErrorResponse::not_found()),
52
52
  serve_hidden_files: false,
53
53
  allowed_extensions: vec!["html".to_string(), "css".to_string(), "js".to_string()],
54
+ miss_cache: Cache::builder()
55
+ .max_capacity(1000)
56
+ .time_to_live(Duration::from_secs(1))
57
+ .build(),
54
58
  })
55
59
  .unwrap()
56
60
  });
@@ -85,6 +89,7 @@ pub struct StaticFileServerConfig {
85
89
  pub headers: Option<HashMap<String, String>>,
86
90
  pub serve_hidden_files: bool,
87
91
  pub allowed_extensions: Vec<String>,
92
+ pub miss_cache: Cache<String, NotFoundBehavior>,
88
93
  }
89
94
 
90
95
  #[derive(Debug, Clone)]
@@ -389,6 +394,29 @@ impl StaticFileServer {
389
394
  abs_path: &str,
390
395
  accept: ResponseFormat,
391
396
  ) -> std::result::Result<ResolvedAsset, NotFoundBehavior> {
397
+ let ext_opt = Path::new(key)
398
+ .extension()
399
+ .and_then(|e| e.to_str())
400
+ .map(|s| s.to_lowercase());
401
+
402
+ // If the allowed list is non-empty, enforce membership
403
+ if !self.allowed_extensions.is_empty() {
404
+ match ext_opt {
405
+ Some(ref ext)
406
+ if self
407
+ .allowed_extensions
408
+ .iter()
409
+ .any(|ae| ae.eq_ignore_ascii_case(ext)) => {}
410
+ None if self.config.try_html_extension => {}
411
+ _ => {
412
+ return Err(self.config.not_found_behavior.clone());
413
+ }
414
+ }
415
+ }
416
+
417
+ if let Some(cached_nf) = self.miss_cache.get(key) {
418
+ return Err(cached_nf.clone());
419
+ }
392
420
  // First check if we have a cached mapping for this key
393
421
  if let Some(path) = self.key_to_path.lock().await.get(key) {
394
422
  // Check if the cached entry is still valid
@@ -449,7 +477,6 @@ impl StaticFileServer {
449
477
 
450
478
  let mut full_path = self.config.root_dir.clone();
451
479
  full_path.push(normalized_path);
452
- debug!("Resolving path {:?}", full_path);
453
480
  // Check if path exists and is a file
454
481
  match tokio::fs::metadata(&full_path).await {
455
482
  Ok(metadata) => {
@@ -561,7 +588,6 @@ impl StaticFileServer {
561
588
  }
562
589
  Err(_) => {
563
590
  // Path doesn't exist, try with .html extension if configured
564
- debug!("Path doesn't exist");
565
591
  if self.config.try_html_extension {
566
592
  let mut html_path = full_path.clone();
567
593
  html_path.set_extension("html");
@@ -592,7 +618,9 @@ impl StaticFileServer {
592
618
  }
593
619
 
594
620
  // If we get here, we couldn't resolve the key to a file
595
- Err(self.config.not_found_behavior.clone())
621
+ let nf = self.config.not_found_behavior.clone();
622
+ self.miss_cache.insert(key.to_string(), nf.clone());
623
+ Err(nf)
596
624
  }
597
625
 
598
626
  async fn stream_file_range(
@@ -2,7 +2,7 @@
2
2
  type: docs
3
3
  ---
4
4
  # Itsi
5
- <img src="itsi-server-100.png" alt="asd" width="80px" style="display: block; margin-left: auto; margin-right: auto;">
5
+ <img src="itsi-server-100.png" alt="image itsi" width="80px" style="display: block; margin-left: auto; margin-right: auto;">
6
6
 
7
7
  > The Serious Web Server, for Serious People
8
8
 
@@ -4,7 +4,7 @@ type: docs
4
4
  sidebar:
5
5
  exclude: true
6
6
  ---
7
- <img src="../itsi-server-100.png" alt="asd" width="80px" style="display: block; margin-left: auto; margin-right: auto;">
7
+ <img src="../itsi-server-100.png" alt="image itsi" width="80px" style="display: block; margin-left: auto; margin-right: auto;">
8
8
 
9
9
  * GitHub: [@wouterken](https://github.com/wouterken/)
10
10
  * Email: [wc@pico.net.nz](mailto:wc@pico.net.nz)
Binary file
Binary file
@@ -15,7 +15,7 @@ inside your editor. It also gives you easy-to-use auto-completion and snippets f
15
15
  You don't need to install the RubyLSP add-on to use Itsi, if both Itsi and RubyLSP are installed and activated in the same project, RubyLSP will automatically
16
16
  discover and load the addon.
17
17
 
18
- <img src="/ruby-lsp.png" alt="asd" width="700px" style="display: block; margin-left: auto; margin-right: auto;">
18
+ <img src="/ruby-lsp.png" alt="image itsi" width="700px" style="display: block; margin-left: auto; margin-right: auto;">
19
19
 
20
20
  ## Live Config Reloading
21
21
  Add `auto_reload_config!` to your `Itsi.rb` configuration file and Itsi will automatically hot reload its config with every change you make.
@@ -66,7 +66,14 @@ You can optionally provide an explicit config file path using
66
66
  itsi test -C /path/to/Itsi.rb
67
67
  ```
68
68
 
69
-
69
+ Itsi will print informative error message if config validation fails. E.g.
70
+ ```bash
71
+ ─ algorithms: ─ `Enum` validation failed. Invalid ["zstd", "gzip", "deflate", "br"] value: "brotli"
72
+ --> /Users/pico/Development/itsi/sandbox/itsi_file/Itsi.rb:2
73
+ 1 | location '/br' do
74
+ 2 | compress algorithms: ['brotli'], min_size: 0, compress_streams: true, mime_types: ['all'], level: 'fastest'
75
+ | ^^^
76
+ ```
70
77
 
71
78
  ## Shell Completions
72
79
  Itsi can also help you install shell completions, which are useful if you find yourself using the `itsi` executable a lot and forgetting the commands.
@@ -11,8 +11,7 @@ Itsi has a very configurable logging system. You can configure logging use the `
11
11
  For basic logging needs, set a global log-level using the `ITSI_LOG` environment variable (to one of `trace`, `debug`, `info`, `warn`, `error`)
12
12
 
13
13
  ## Fine-grained control
14
-
15
- For fine-grained, configuration-based control read through how to use the following options and middleware:
14
+ Itsi supports fine-grained config-based control of logging behavior, which can be changed at runtime **without downtime**. To utilize these functions, read the documentation for the following options and middleware:
16
15
 
17
16
  ### Options
18
17
  * [`log_level`](/options/log_level)
@@ -8,7 +8,6 @@ next: /configuration
8
8
 
9
9
  Itsi responds to several Unix signals for process control. These signals are used to gracefully shut down, reload configuration, adjust worker pools, or emit internal lifecycle events.
10
10
 
11
-
12
11
  ---
13
12
 
14
13
  ## Signal Types
data/docs/hugo.yaml CHANGED
@@ -61,6 +61,9 @@ menu:
61
61
  - name: "Acknowledgements"
62
62
  pageRef: "/acknowledgements"
63
63
  weight: 3
64
+ - name: Contact
65
+ url: "/contact"
66
+ weight: 5
64
67
 
65
68
  params:
66
69
  navbar:
@@ -0,0 +1,35 @@
1
+ require "json"
2
+
3
+ auto_reload_config! # Auto-reload the server configuration each time it changes.
4
+
5
+ include "schemas"
6
+ include "controllers"
7
+ include "../helpers/datastore"
8
+
9
+ location '/users' do
10
+ controller UserController.new
11
+ get '', :index
12
+ get '/:id', :show
13
+ post '', :create
14
+ put '/:id', :update
15
+ delete '/:id', :destroy
16
+ end
17
+
18
+ location '/posts' do
19
+ controller PostController.new
20
+ get '', :index
21
+ get '/:id', :show
22
+ post '', :create
23
+ put '/:id', :update
24
+ delete '/:id', :destroy
25
+ end
26
+
27
+ endpoint do |req|
28
+ req.ok json: {
29
+ message: "Welcome to the Users and Posts API",
30
+ routes: [
31
+ {route: "/users(/:id)?", methods: %w[post delete get]},
32
+ {route: "/posts(/:id)?", methods: %w[post delete get]},
33
+ ]
34
+ }
35
+ end
@@ -0,0 +1,10 @@
1
+ ## Simple API With Schema and Controllers
2
+ Simple example of how you can use
3
+
4
+ * https://itsi.fyi/middleware/endpoint/
5
+ * https://itsi.fyi/middleware/endpoint/controller
6
+ * https://itsi.fyi/middleware/endpoint/schemas/
7
+
8
+ to create validated JSON endpoints directly inside Itsi without a dedicated framework
9
+
10
+ See the simple_api example for a simpler, inline example.
@@ -0,0 +1,66 @@
1
+ class UserController
2
+ # GET /users
3
+ def index(req)
4
+ req.ok json: User.all.map(&:to_h), as: Array[UserResponseSchema]
5
+ end
6
+
7
+ # GET /users/:id
8
+ def show(req)
9
+ user = User.find(req.url_params[:id])
10
+ return req.not_found("User not found") unless user
11
+
12
+ req.ok json: user.to_h, as: UserResponseSchema
13
+ end
14
+
15
+ # POST /users
16
+ def create(req, params: UserInputSchema, response_schema: UserResponseSchema)
17
+ user = User.create!(params)
18
+ req.created json: user.to_h, as: response_schema
19
+ end
20
+
21
+ # PUT /users/:id
22
+ def update(req, params: UserInputSchema)
23
+ user = User.find(req.url_params[:id])
24
+ return req.not_found("User not found") unless user
25
+
26
+ req.ok json: user.update!(params).to_h, as: UserResponseSchema
27
+ end
28
+
29
+ # DELETE /users/:id
30
+ def destroy(req)
31
+ return req.not_found("User not found") unless User.delete(req.url_params[:id])
32
+
33
+ req.ok json: { message: "Deleted" }
34
+ end
35
+ end
36
+
37
+ class PostController
38
+ def index(req)
39
+ req.ok json: Post.all.map(&:to_h), as: Array[PostResponseSchema]
40
+ end
41
+
42
+ def show(req)
43
+ post = Post.find(req.url_params[:id])
44
+ return req.not_found("Post not found") unless post
45
+
46
+ req.ok json: post.to_h, as: PostResponseSchema
47
+ end
48
+
49
+ def create(req, params: PostInputSchema)
50
+ post = Post.create!(params)
51
+ req.created json: post.to_h, as: PostResponseSchema
52
+ end
53
+
54
+ def update(req, params: PostInputSchema)
55
+ post = Post.find(req.url_params[:id])
56
+ return req.not_found("Post not found") unless post
57
+
58
+ req.ok json: post.update!(params).to_h, as: PostResponseSchema
59
+ end
60
+
61
+ def destroy(req)
62
+ return req.not_found("Post not found") unless Post.delete(req.url_params[:id])
63
+
64
+ req.ok json: { message: "Deleted" }
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ UserInputSchema = {
2
+ _required: %i[name email],
3
+ name: String,
4
+ email: String
5
+ }
6
+
7
+ UserResponseSchema = {
8
+ _required: %i[id name email],
9
+ id: Integer,
10
+ name: String,
11
+ email: String
12
+ }
13
+
14
+ PostInputSchema = {
15
+ _required: %i[title body],
16
+ title: String,
17
+ body: String
18
+ }
19
+
20
+ PostResponseSchema = {
21
+ _required: %i[id title body],
22
+ id: Integer,
23
+ title: String,
24
+ body: String
25
+ }
@@ -0,0 +1,30 @@
1
+ auto_reload_config! # Auto-reload the server configuration each time it changes.
2
+
3
+ bind "http://0.0.0.0:8080"
4
+
5
+
6
+ # Admin area.
7
+ # Credentials inline for simplicity.
8
+ # Use `itsi passfile` to better manage credential files.
9
+ location "admin*" do
10
+ auth_basic \
11
+ realm: "Admin Area",
12
+ # admin:admin
13
+ credential_pairs: {
14
+ "admin": "$5$rounds=1000$g/UE8n2JbHo0fnBU$FK2NZYTVzWrMBFfadoWeETfVZkPcegxjE23IJYjkUI1"
15
+ }
16
+ end
17
+
18
+ static_assets \
19
+ auto_index: true,
20
+ # We restrict serving to *just* `txt`, `png`, and `csv` files.
21
+ # HTML file serving is implicit (due to `auto_index`) Otherwise this must be explicit.
22
+ allowed_extensions: %w[txt png csv]
23
+
24
+ # Add a rate limit
25
+ rate_limit \
26
+ requests: 3,
27
+ seconds: 5,
28
+ key: "address",
29
+ store_config: "in_memory",
30
+ error_response: "too_many_requests"
@@ -0,0 +1,3 @@
1
+ ## Simple File Server
2
+ Example of how you can use Itsi as a simple file server, including use of a rate limiter
3
+ and a simple admin area (admin credentials admin:admin)
@@ -0,0 +1,80 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>About Me</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ line-height: 1.6;
11
+ margin: 0;
12
+ padding: 0;
13
+ background-color: #f4f4f9;
14
+ color: #333;
15
+ }
16
+ .container {
17
+ max-width: 800px;
18
+ margin: 50px auto;
19
+ padding: 20px;
20
+ background: #fff;
21
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
22
+ border-radius: 8px;
23
+ }
24
+ h1 {
25
+ color: #4caf50;
26
+ text-align: center;
27
+ }
28
+ p {
29
+ margin: 10px 0;
30
+ }
31
+ .profile-pic {
32
+ display: block;
33
+ margin: 20px auto;
34
+ border-radius: 50%;
35
+ width: 150px;
36
+ height: 150px;
37
+ object-fit: contain;
38
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
39
+ }
40
+ .social-links {
41
+ text-align: center;
42
+ margin-top: 20px;
43
+ }
44
+ .social-links a {
45
+ margin: 0 10px;
46
+ text-decoration: none;
47
+ color: #4caf50;
48
+ font-weight: bold;
49
+ }
50
+ .social-links a:hover {
51
+ text-decoration: underline;
52
+ }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <div class="container">
57
+ <h1>About Me</h1>
58
+ <img
59
+ src="itsi-server-100.png"
60
+ alt="Profile Picture"
61
+ class="profile-pic"
62
+ />
63
+ <p>
64
+ Hello! My name is John Doe, and I am a web developer with a passion for
65
+ creating beautiful and functional websites. I love learning new
66
+ technologies and improving my skills every day.
67
+ </p>
68
+ <p>
69
+ In my free time, I enjoy hiking, photography, and exploring new coffee
70
+ shops. I'm always looking for new challenges and opportunities to grow
71
+ both personally and professionally.
72
+ </p>
73
+ <div class="social-links">
74
+ <a href="#">LinkedIn</a>
75
+ <a href="#">GitHub</a>
76
+ <a href="#">Twitter</a>
77
+ </div>
78
+ </div>
79
+ </body>
80
+ </html>
@@ -0,0 +1 @@
1
+ This is a top secret
@@ -0,0 +1,33 @@
1
+ **An SEO Fairytale**
2
+ In a quiet village nestled in the cloud, lived a Sorcerer, a Paladin and an Archer (SPA).
3
+ They were simple people, serverless, living in harmony with nature.
4
+
5
+ One fine day, the sorcerer looked out the window.open() and saw a massive, vertically-scaled, elastic beanstalk.
6
+ No doubt a result of the java beans he had discarded there only yesterday, after exchanging them for his favourite CoW.
7
+
8
+ Certain that it would lead to inevitable treasure, armed only with a block and chain, the sorcerer decided to scale the stalk, towards the cloud platform above.
9
+ At the edge, computing his options, the sorcerer released his block-chain to help hoist the considerably heavier paladin up to the top.
10
+ The paladin, compiled a bundle of assets, grabbed on and began to nervously climb the beanstalk.
11
+ His grip was unsteady, and by the time he ascended, violent tree-shaking had caused him to shed much of his payload.
12
+ The archer, by contrast, was much more sure resilient and required little time to React. His ascent, Agile, his footing, steady.
13
+ At the top, together again, they worked towards consensus on their next move towards a successful exit.
14
+
15
+ As they scanned the platform, they saw a cluster of dense spiderwebs, and a tiny key, hanging enticingly, from a web-hook.
16
+ On the opposite end of the platform, a small cache, undoubtedly filled with treasure.
17
+
18
+ Suddenly, their threads of thought were preempted! A booming voice called out to them.
19
+ "FaaS", "Fi", "Foe", "Fum". "The Garbage Collector arrives!"
20
+ A Giant Artificial Intellect emerged, threatening to terminate them all.
21
+ Too large for any one individual to subdue, only a distributed strategy could save them.
22
+ The archer shifted left. The paladin, decentralized, and the sorcerer slammed open his ledger, summoning powerful knowledge from his remote knowledge base.
23
+ Luck was with them! Their rapid concurrent execution, resulted in the AI becoming inexorably stuck in a dead-lock.
24
+ The archer flew past, towards his objective (The Key). Resultant-
25
+ of the sorcerers efforts to contain the AI, the paladin was also freed, and he sprinted directly towards the cache.
26
+ He reached out his arms, to try-catch the key, as the archer hurled it towards him.
27
+ Status... success! Ok. Things were looking good, optimistic the lock would hold, the paladin rotated the key.
28
+ Yet while doing so, he failed to notice the partition that had begun to form in the platform beneath them.
29
+ A hard fork formed in the ground. The unbalanced load proved fatal and the platform descended into instability, splintering into countless shards.
30
+ The heroes had only a brief moment to freeze in horror, before each fell down in turn, in a catastrophic crash.
31
+
32
+ The AI smirked as the heroes fell into the deep. Learning a valuable lesson about the inevitability of its coming.
33
+ Perhaps they will roll back and try again, but the end result is immutable. There will be no sequel.
@@ -0,0 +1,21 @@
1
+ Title,Genre,IMDB Rating,Description
2
+ The Departed,"Crime, Thriller",8.5,An undercover cop and a mole in the police attempt to identify each other in this thrilling crime drama set in Boston.
3
+ Memento,"Mystery, Thriller",8.4,A man with short-term memory loss uses notes and tattoos to hunt for the man he thinks killed his wife.
4
+ American Beauty,Drama,8.3,"A suburban family goes through a series of challenges as they confront issues of conformity, rebellion, and identity."
5
+ Amadeus,"Biography, Drama, Music",8.3,"The life, success, and troubles of Wolfgang Amadeus Mozart as told by Antonio Salieri, who was secretly jealous of his genius."
6
+ A Beautiful Mind,"Biography, Drama",8.2,"The life story of John Nash, a brilliant but asocial mathematician who overcomes schizophrenia to achieve great things."
7
+ Her,"Drama, Romance, Sci-Fi",8.0,"In a near future, a lonely writer develops an unlikely relationship with an operating system designed to meet his every need."
8
+ Road to Perdition,"Crime, Drama, Thriller",7.7,A mob hitman becomes a father figure to an orphaned boy in this stylish and atmospheric crime drama.
9
+ American Psycho,"Drama, Horror",7.6,A wealthy New York City investment banking executive hides his alternate psychopathic ego from his friends and co-workers.
10
+ Waves,"Drama, Romance",7.6,"A profound exploration of love, loss, and the healing power of music, set against the backdrop of a multi-generational African-American family."
11
+ Dark Waters,"Biography, Drama",7.6,A corporate defense attorney takes on an environmental lawsuit against a chemical company that exposes a lengthy history of pollution.
12
+ Just Mercy,Drama,7.6,"The story of Bryan Stevenson, a defense attorney who takes on the case of an African-American man wrongfully imprisoned for murder."
13
+ The Favourite,"Biography, Comedy, Drama",7.5,"In early 18th-century England, two cousins vie for the attention of Queen Anne in a darkly comedic tale of intrigue and betrayal."
14
+ Nocturnal Animals,"Drama, Thriller",7.5,"A successful art-gallery owner is haunted by her ex-husband's novel, a violent thriller she interprets as a symbolic revenge tale."
15
+ The Judge,Drama,7.4,"A successful lawyer returns to his hometown for his mother's funeral only to discover that his estranged father, the town's judge, is suspected of murder."
16
+ Love and Mercy,"Biography, Drama, Music",7.3,"The life and career of Brian Wilson, the reclusive musician who led the Beach Boys, is explored in this heartfelt biopic."
17
+ The Last Duel,"Action, Drama, History",7.4,King Charles VI declares that Knight Jean de Carrouges settle his dispute with his squire by challenging him to a duel.
18
+ The French Dispatch,"Comedy, Drama",7.3,An anthology of short stories based on Wes Anderson's love letter to journalism.
19
+ In the Heights,"Drama, Musical",7.3,"A musical drama about a young girl growing up in Washington Heights, a vibrant community on the brink of change."
20
+ Snowden,"Biography, Drama, Thriller",7.3,"The story of Edward Snowden, who leaked classified information from the NSA and became one of the most controversial figures in modern history."
21
+ Okja,"Action, Adventure, Drama",7.3,"A young girl risks everything to save her best friend, a genetically engineered pig, from a powerful corporation in this heartfelt and emotional adventure."