itsi 0.1.20 → 0.2.3

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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -8
  3. data/Cargo.lock +29 -30
  4. data/LICENSE.txt +698 -0
  5. data/README.md +16 -4
  6. data/Rakefile +9 -5
  7. data/crates/itsi_acme/.gitignore +4 -0
  8. data/crates/itsi_acme/Cargo.toml +86 -0
  9. data/crates/itsi_acme/LICENSE-APACHE +201 -0
  10. data/crates/itsi_acme/LICENSE-MIT +23 -0
  11. data/crates/itsi_acme/README.md +9 -0
  12. data/crates/itsi_acme/examples/high_level.rs +63 -0
  13. data/crates/itsi_acme/examples/high_level_warp.rs +52 -0
  14. data/crates/itsi_acme/examples/low_level.rs +87 -0
  15. data/crates/itsi_acme/examples/low_level_axum.rs +66 -0
  16. data/crates/itsi_acme/src/acceptor.rs +81 -0
  17. data/crates/itsi_acme/src/acme.rs +354 -0
  18. data/crates/itsi_acme/src/axum.rs +86 -0
  19. data/crates/itsi_acme/src/cache.rs +39 -0
  20. data/crates/itsi_acme/src/caches/boxed.rs +80 -0
  21. data/crates/itsi_acme/src/caches/composite.rs +69 -0
  22. data/crates/itsi_acme/src/caches/dir.rs +106 -0
  23. data/crates/itsi_acme/src/caches/mod.rs +11 -0
  24. data/crates/itsi_acme/src/caches/no.rs +78 -0
  25. data/crates/itsi_acme/src/caches/test.rs +136 -0
  26. data/crates/itsi_acme/src/config.rs +172 -0
  27. data/crates/itsi_acme/src/https_helper.rs +69 -0
  28. data/crates/itsi_acme/src/incoming.rs +142 -0
  29. data/crates/itsi_acme/src/jose.rs +161 -0
  30. data/crates/itsi_acme/src/lib.rs +142 -0
  31. data/crates/itsi_acme/src/resolver.rs +59 -0
  32. data/crates/itsi_acme/src/state.rs +424 -0
  33. data/crates/itsi_rb_helpers/src/lib.rs +4 -3
  34. data/crates/itsi_scheduler/Cargo.toml +1 -1
  35. data/crates/itsi_scheduler/src/itsi_scheduler.rs +8 -2
  36. data/crates/itsi_scheduler/src/lib.rs +1 -0
  37. data/crates/itsi_server/Cargo.toml +1 -1
  38. data/crates/itsi_server/src/lib.rs +2 -1
  39. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +18 -1
  40. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +11 -3
  41. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +148 -66
  42. data/crates/itsi_server/src/ruby_types/itsi_server.rs +2 -0
  43. data/crates/itsi_server/src/server/binds/bind.rs +3 -0
  44. data/crates/itsi_server/src/server/binds/listener.rs +12 -5
  45. data/crates/itsi_server/src/server/binds/tls.rs +13 -5
  46. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +12 -5
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +8 -1
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +9 -1
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +48 -43
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +11 -2
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +39 -12
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +36 -27
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +25 -11
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +12 -3
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +74 -72
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +15 -1
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +11 -8
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +19 -11
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +5 -5
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +2 -2
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +11 -5
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +17 -20
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +19 -8
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +16 -37
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +22 -12
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +26 -11
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +7 -1
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +14 -4
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +19 -0
  70. data/crates/itsi_server/src/server/middleware_stack/mod.rs +49 -13
  71. data/crates/itsi_server/src/server/mod.rs +1 -0
  72. data/crates/itsi_server/src/server/redirect_type.rs +26 -0
  73. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +22 -16
  74. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +49 -12
  75. data/crates/itsi_server/src/server/signal.rs +1 -0
  76. data/crates/itsi_server/src/server/size_limited_incoming.rs +6 -0
  77. data/crates/itsi_server/src/server/thread_worker.rs +5 -1
  78. data/crates/itsi_server/src/services/itsi_http_service.rs +20 -2
  79. data/crates/itsi_server/src/services/rate_limiter.rs +15 -4
  80. data/crates/itsi_server/src/services/static_file_server.rs +33 -19
  81. data/crates/itsi_tracing/src/lib.rs +42 -22
  82. data/docs/content/_index.md +2 -3
  83. data/docs/content/acknowledgements/_index.md +5 -2
  84. data/docs/content/configuration/_index.md +8 -5
  85. data/docs/content/contact/_index.md +8 -1
  86. data/docs/content/directory_listing.jpg +0 -0
  87. data/docs/content/error_page.jpg +0 -0
  88. data/docs/content/faqs/_index.md +5 -3
  89. data/docs/content/features/_index.md +56 -50
  90. data/docs/content/getting_started/_index.md +8 -5
  91. data/docs/content/getting_started/local_development.md +76 -9
  92. data/docs/content/getting_started/logging.md +15 -9
  93. data/docs/content/getting_started/running_itsi_in_production.md +5 -3
  94. data/docs/content/getting_started/signals.md +37 -0
  95. data/docs/content/itsi_scheduler/_index.md +8 -7
  96. data/docs/content/utilities/_index.md +13 -0
  97. data/docs/content/utilities/config_file_testing.md +17 -0
  98. data/docs/content/utilities/passfile_generator.md +41 -0
  99. data/docs/content/utilities/route_testing.md +27 -0
  100. data/docs/content/utilities/secrets_management.md +30 -0
  101. data/docs/hugo.yaml +4 -1
  102. data/fairytale.txt +3 -4
  103. data/gems/scheduler/Cargo.lock +74 -17
  104. data/gems/scheduler/README.md +4 -5
  105. data/gems/scheduler/Rakefile +0 -4
  106. data/gems/scheduler/itsi-scheduler.gemspec +2 -2
  107. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  108. data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
  109. data/gems/scheduler/test/test_active_record.rb +12 -7
  110. data/gems/server/Cargo.lock +28 -29
  111. data/gems/server/Rakefile +0 -4
  112. data/gems/server/exe/itsi +13 -2
  113. data/gems/server/itsi-server.gemspec +2 -2
  114. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
  115. data/gems/server/lib/itsi/http_request.rb +58 -30
  116. data/gems/server/lib/itsi/http_response.rb +10 -7
  117. data/gems/server/lib/itsi/passfile.rb +6 -7
  118. data/gems/server/lib/itsi/server/config/config_helpers.rb +41 -29
  119. data/gems/server/lib/itsi/server/config/dsl.rb +22 -442
  120. data/gems/server/lib/itsi/server/config/known_paths.rb +14 -7
  121. data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
  122. data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
  123. data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
  124. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
  125. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
  126. data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
  127. data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
  128. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
  129. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
  130. data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
  131. data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
  132. data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
  133. data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
  134. data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
  135. data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
  136. data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
  137. data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
  138. data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
  139. data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
  140. data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
  141. data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
  142. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
  143. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
  144. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
  145. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
  146. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
  147. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
  148. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
  149. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
  150. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
  151. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
  152. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
  153. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
  154. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
  155. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
  156. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
  157. data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
  158. data/gems/server/lib/itsi/server/config/middleware/error_response.md +74 -0
  159. data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
  160. data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
  161. data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
  162. data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
  163. data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
  164. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
  165. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
  166. data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
  167. data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
  168. data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
  169. data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
  170. data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
  171. data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
  172. data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
  173. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
  174. data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
  175. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
  176. data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
  177. data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
  178. data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
  179. data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
  180. data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
  181. data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
  182. data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
  183. data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
  184. data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
  185. data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
  186. data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
  187. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +113 -0
  188. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
  189. data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
  190. data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
  191. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
  192. data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
  193. data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
  194. data/gems/server/lib/itsi/server/config/option.rb +4 -0
  195. data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
  196. data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
  197. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
  198. data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
  199. data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
  200. data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
  201. data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
  202. data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
  203. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
  204. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
  205. data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
  206. data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
  207. data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
  208. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
  209. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
  210. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
  211. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
  212. data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
  213. data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
  214. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
  215. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
  216. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
  217. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
  218. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
  219. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
  220. data/gems/server/lib/itsi/server/config/options/include.md +20 -0
  221. data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
  222. data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
  223. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
  224. data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
  225. data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
  226. data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
  227. data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
  228. data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
  229. data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
  230. data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
  231. data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
  232. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
  233. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
  234. data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
  235. data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
  236. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
  237. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
  238. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
  239. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
  240. data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
  241. data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
  242. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
  243. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
  244. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
  245. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
  246. data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
  247. data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
  248. data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
  249. data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
  250. data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
  251. data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
  252. data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
  253. data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
  254. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
  255. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
  256. data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
  257. data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
  258. data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
  259. data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
  260. data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
  261. data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
  262. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
  263. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
  264. data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
  265. data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
  266. data/gems/server/lib/itsi/server/config/typed_struct.rb +68 -32
  267. data/gems/server/lib/itsi/server/config.rb +163 -119
  268. data/gems/server/lib/itsi/server/default_app/default_app.rb +1 -1
  269. data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
  270. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +4 -5
  271. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +10 -4
  272. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +2 -3
  273. data/gems/server/lib/itsi/server/rack_interface.rb +0 -1
  274. data/gems/server/lib/itsi/server/route_tester.rb +61 -9
  275. data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
  276. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
  277. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +13 -10
  278. data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
  279. data/gems/server/lib/itsi/server/version.rb +1 -1
  280. data/gems/server/lib/itsi/server.rb +111 -27
  281. data/gems/server/lib/itsi/standard_headers.rb +80 -80
  282. data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
  283. data/gems/server/test/helpers/test_helper.rb +90 -29
  284. data/gems/server/test/middleware/allow_list.rb +128 -0
  285. data/gems/server/test/middleware/auth_api_key.rb +141 -0
  286. data/gems/server/test/middleware/auth_basic.rb +91 -0
  287. data/gems/server/test/middleware/auth_jwt.rb +214 -0
  288. data/gems/server/test/middleware/cache_control.rb +82 -0
  289. data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
  290. data/gems/server/test/middleware/compression.rb +89 -0
  291. data/gems/server/test/middleware/cors.rb +113 -0
  292. data/gems/server/test/middleware/csp.rb +62 -0
  293. data/gems/server/test/middleware/deny_list.rb +131 -0
  294. data/gems/server/test/middleware/endpoint.rb +300 -0
  295. data/gems/server/test/middleware/etag.rb +75 -0
  296. data/gems/server/test/middleware/grpc/grpc.rb +158 -0
  297. data/gems/server/test/middleware/grpc/test_service.proto +32 -0
  298. data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
  299. data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
  300. data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
  301. data/gems/server/test/middleware/header_interpolation.rb +35 -0
  302. data/gems/server/test/middleware/intrusion_protection.rb +259 -0
  303. data/gems/server/test/middleware/location.rb +220 -0
  304. data/gems/server/test/middleware/max_body.rb +20 -0
  305. data/gems/server/test/middleware/proxy.rb +415 -0
  306. data/gems/server/test/middleware/rate_limit.rb +211 -0
  307. data/gems/server/test/middleware/redirect.rb +85 -0
  308. data/gems/server/test/middleware/request_headers.rb +50 -0
  309. data/gems/server/test/middleware/response_headers.rb +50 -0
  310. data/gems/server/test/middleware/static_assets.rb +374 -0
  311. data/gems/server/test/middleware/static_response.rb +56 -0
  312. data/gems/server/test/middleware/string_rewrite.rb +112 -0
  313. data/gems/server/test/middleware/test_log_requests.rb +54 -2
  314. data/gems/server/test/options/bind.rb +47 -0
  315. data/gems/server/test/options/header_read_timeout.rb +23 -0
  316. data/gems/server/test/options/test_request_timeout.rb +16 -0
  317. data/gems/server/test/options/test_workers.rb +11 -6
  318. data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
  319. data/lib/itsi/version.rb +1 -1
  320. data/tasks.txt +15 -72
  321. metadata +209 -10
  322. data/examples/static_assets_example.rb +0 -83
  323. data/gems/server/lib/itsi/server/default_config/Itsi-rackup.rb +0 -119
@@ -1,7 +1,6 @@
1
1
  module Itsi
2
2
  class Server
3
3
  module RackInterface
4
-
5
4
  # Builds a handler proc that is compatible with Rack applications.
6
5
  def self.for(app)
7
6
  require "rack"
@@ -1,16 +1,68 @@
1
1
  module Itsi
2
2
  class Server
3
+ # Utility module for printing Itsi route information
3
4
  module RouteTester
4
-
5
5
  require "set"
6
6
  require "strscan"
7
-
7
+ require "debug"
8
8
  def format_mw(mw)
9
- case mw["type"]
9
+ mw_name, mw_args = mw
10
+ case mw_name
10
11
  when "app"
11
- "app #{mw["parameters"]["app_proc"].inspect.split(" ")[1]}"
12
+ "\e[33mapp\e[0m(#{mw_args["app_proc"].inspect.split(" ")[1][0...-1]})"
13
+ when "log_requests"
14
+ if mw_args["before"] && mw_args["after"]
15
+ "\e[33mlog_requests\e[0m(before: #{mw_args["before"]["format"][0..6]}..., after: #{mw_args["after"]["format"][0..6]}...)"
16
+ elsif mw_args["before"]
17
+ "\e[33mlog_requests\e[0m(before: #{mw_args["before"]["format"][0..6]}...)"
18
+ elsif mw_args["after"]
19
+ "\e[33mlog_requests\e[0m(before: nil, after: #{mw_args["after"]["format"][0..6]}...)"
20
+ end
21
+ when "compress"
22
+ "\e[33mcompress\e[0m(#{mw_args["algorithms"].join(" ")}, #{mw_args["mime_types"]})"
23
+ when "cors"
24
+ "\e[33mcors\e[0m(#{mw_args["allow_origins"].join(" ")}, #{mw_args["allow_methods"].join(" ")})"
25
+ when "etag"
26
+ "\e[33metag\e[0m(#{mw_args["type"]}/#{mw_args["algorithm"]}, #{mw_args["handle_if_none_match"] ? "if_none_match" : ""})"
27
+ when "cache_control"
28
+ "\e[33mcache_control\e[0m(max_age: #{mw_args["max_age"]}, #{mw_args.select do |_, v|
29
+ v == true
30
+ end.keys.join(", ")})"
31
+ when "redirect"
32
+ "\e[33mredirect\e[0m(to: #{mw_args["to"]}, type: #{mw_args["type"]})"
33
+ when "static_assets"
34
+ "\e[33mstatic_assets\e[0m(path: #{mw_args["root_dir"]})"
35
+ when "auth_api_key"
36
+ "\e[33mauth_api_key\e[0m(keys: #{mw_args["valid_keys"].keys}#{mw_args["credentials_file"] ? ", credentials_file: #{mw_args["credentials_file"]}" : ""})"
37
+ when "auth_basic"
38
+ "\e[33mbasic_auth\e[0m(keys: #{mw_args["realm"]}#{mw_args["credentials_file"] ? ", credentials_file: #{mw_args["credentials_file"]}" : ""})"
39
+ when "auth_jwt"
40
+ "\e[33mjwt_auth\e[0m(#{mw_args["verifiers"].keys.join(",")})"
41
+ when "rate_limit"
42
+ key = mw_args["key"].is_a?(Hash) ? mw_args["key"]["parameter"] : mw_args["key"]
43
+ "\e[33mrate_limit\e[0m(rps: #{mw_args["requests"]}/#{mw_args["seconds"]}, key: #{key})"
44
+ when "allow_list"
45
+ "\e[33mallow_list\e[0m(patterns: #{mw_args["allowed_patterns"].join(", ")})"
46
+ when "deny_list"
47
+ "\e[33mdeny_list\e[0m(patterns: #{mw_args["denied_patterns"].join(", ")})"
48
+ when "csp"
49
+ "\e[33mcsp\e[0m(#{mw_args["policy"].map { |k, v| "#{k}: #{v.join(",")}" }.join(", ")})"
50
+ when "intrusion_protection"
51
+ [mw_args].flatten.map do |mw_args|
52
+ "\e[33mintrusion_protection\e[0m(banned_url_patterns: #{mw_args["banned_url_patterns"]&.length}, banned_header_patterns: #{mw_args["banned_header_patterns"]&.keys&.join(", ")}, #{mw_args["banned_time_seconds"]}s)"
53
+ end.join("\n")
54
+ when "request_headers"
55
+ [mw_args].flatten.map do |mw_args|
56
+ "\e[33mrequest_headers\e[0m(added: #{mw_args["additions"].keys}, removed: #{mw_args["removals"]})"
57
+ end.join("\n")
58
+ when "response_headers"
59
+ [mw_args].flatten.map do |mw_args|
60
+ "\e[33mresponse_headers\e[0m(added: #{mw_args["additions"].keys}, removed: #{mw_args["removals"]})"
61
+ end.join("\n")
62
+ when "static_response"
63
+ "\e[response_headers\e[0m(#{mw_args["code"]} body: #{mw_args["body"][0..10]})"
12
64
  else
13
- mw["type"]
65
+ "\e[33m#{mw.first}\e[0m"
14
66
  end
15
67
  end
16
68
 
@@ -21,12 +73,12 @@ module Itsi
21
73
  end.compact
22
74
  filter_str = filters.any? ? filters.join(", ") : "(none)"
23
75
 
24
- middlewares = stack["middleware"]
76
+ middlewares = stack["middleware"].to_a
25
77
 
26
78
  puts "─" * 76
27
- puts "Route: #{route_str}"
28
- puts "Conditions: #{filter_str}"
29
- puts "Middleware: • #{format_mw(middlewares.first)}"
79
+ puts "\e[32mRoute:\e[0m \e[33m#{route_str}\e[0m"
80
+ puts "\e[32mConditions:\e[0m \e[34m#{filter_str}\e[0m"
81
+ puts "\e[32mMiddleware:\e[0m • #{format_mw(middlewares.first)}"
30
82
  middlewares[1..].each do |mw|
31
83
  puts " • #{format_mw(mw)}"
32
84
  end
@@ -19,7 +19,7 @@ module Itsi
19
19
  end
20
20
 
21
21
  def server_running?
22
- Itsi::Server.respond_to?(:running) && Itsi::Server.running?
22
+ Itsi::Server.respond_to?(:running?) && Itsi::Server.running?
23
23
  end
24
24
  end
25
25
  end
@@ -23,21 +23,21 @@ module Itsi
23
23
  case v
24
24
  when true, 'true', '1', 1 then true
25
25
  when false, 'false', '0', 0 then false
26
- else raise "Cannot cast #{v.inspect} to Boolean"
26
+ else raise ValidationError.new("Cannot cast #{v.inspect} to Boolean")
27
27
  end
28
28
  },
29
29
  FalseClass => ->(v){
30
30
  case v
31
31
  when true, 'true', '1', 1 then true
32
32
  when false, 'false', '0', 0 then false
33
- else raise "Cannot cast #{v.inspect} to Boolean"
33
+ else raise ValidationError.new("Cannot cast #{v.inspect} to Boolean")
34
34
  end
35
35
  },
36
36
  :Boolean => ->(v){
37
37
  case v
38
38
  when true, 'true', '1', 1 then true
39
39
  when false, 'false', '0', 0 then false
40
- else raise "Cannot cast #{v.inspect} to Boolean"
40
+ else raise ValidationError.new("Cannot cast #{v.inspect} to Boolean")
41
41
  end
42
42
  },
43
43
  Date => ->(v){ Date.parse(v.to_s) },
@@ -45,17 +45,6 @@ module Itsi
45
45
  DateTime => ->(v){ DateTime.parse(v.to_s) }
46
46
  }.compare_by_identity
47
47
 
48
- # Extracts the expected type and required flag from a schema definition.
49
- def extract_schema(schema_def)
50
- if schema_def.is_a?(Hash) && schema_def.key?(:_type)
51
- expected_type = schema_def[:_type]
52
- required = schema_def.fetch(:required, true)
53
- [expected_type, required]
54
- else
55
- [schema_def, true]
56
- end
57
- end
58
-
59
48
  # Preprocess the schema into fixed keys (as symbols) and regex keys.
60
49
  # Memoizes the result based on the schema.
61
50
  def processed_schema(schema)
@@ -63,8 +52,9 @@ module Itsi
63
52
  @@schema_cache[schema] ||= begin
64
53
  fixed = {}
65
54
  regex = []
55
+ required_params = schema[:_required] || []
66
56
  schema.each do |k, schema_def|
67
- expected_type, required = extract_schema(schema_def)
57
+ expected_type, required = schema_def, required_params.include?(k)
68
58
  if k.is_a?(Regexp)
69
59
  regex << [k, [expected_type, required]]
70
60
  else
@@ -179,16 +169,22 @@ module Itsi
179
169
  end
180
170
 
181
171
  # Process regex keys (only string keys not already handled as fixed keys).
182
- params.keys.select { |k| k.is_a?(String) }.each do |key|
172
+ params.keys.each do |key|
173
+ if key == :_required
174
+ params.delete(key)
175
+ next
176
+ end
183
177
  next if fixed_schema.has_key?(key.to_sym) || fixed_schema.has_key?(key)
184
- regex_schema.each do |regex, (expected_type, _required)|
178
+ unless regex_schema.find do |regex, (expected_type, _required)|
185
179
  if regex.match(key)
186
180
  new_path = path + [key]
187
181
  err = cast_value!(params, key, expected_type, new_path)
188
182
  errors << err if err
189
- break # only use the first matching regex
183
+ true # only use the first matching regex
190
184
  end
191
185
  end
186
+ params.delete(key)
187
+ end
192
188
  end
193
189
 
194
190
  raise ValidationError.new(errors) unless errors.empty?
@@ -2,37 +2,41 @@ module Itsi
2
2
  class Server
3
3
  module TypedHandlers
4
4
  module SourceParser
5
- require 'prism'
6
-
5
+ require "prism"
7
6
 
7
+ # Source Parser interprets endpoint handlers
8
+ # and extracts arity and schema information.
8
9
  def self.extract_expr_from_source_location(proc)
9
- source_lines = IO.readlines(proc.source_location.first)
10
+ source_location = proc.source_location
11
+ source_lines = IO.readlines(source_location.first)
10
12
 
11
13
  proc_line = source_location.last - 1
12
14
  first_line = source_lines[proc_line]
13
15
 
14
- until first_line =~ /(?:lambda|proc|->|def|.*?do\s*\|)/ || proc_line.zero?
16
+ until first_line =~ /(?:lambda|proc|->|def|.*?do\s*|.*?\{.*?\|)/ || proc_line.zero?
15
17
  proc_line -= 1
16
18
  first_line = source_lines[proc_line]
17
19
  end
18
20
  lines = source_lines[proc_line..]
19
- lines[0] = lines[0][/(?:lambda|proc|->|def|.*?do\s*\|).*/]
21
+ lines[0] = lines[0][/(?:lambda|proc|->|def|.*?do\s+|.*?\{.*?\|).*/]
20
22
  src_str = lines.first << "\n"
21
23
  intermediate = Prism.parse(src_str)
22
24
 
23
25
  lines[1..-1].each do |line|
24
26
  break if intermediate.success?
27
+
25
28
  token_count = 0
26
29
  line.split(/(?=\s|;|\)|\})/).each do |token|
27
30
  src_str << token
28
31
  token_count += 1
29
32
  intermediate = Prism.parse(src_str)
30
33
  next unless intermediate.success? && token_count > 1
34
+
31
35
  break
32
36
  end
33
37
  end
34
38
 
35
- raise 'Source Extraction Failed' unless intermediate.success?
39
+ raise "Source Extraction Failed" unless intermediate.success?
36
40
 
37
41
  src = intermediate.value.statements.body.first.yield_self do |s|
38
42
  s.type == :call_node ? s.block : s
@@ -40,14 +44,13 @@ module Itsi
40
44
  params = src.parameters
41
45
  params = params.parameters if params.respond_to?(:parameters)
42
46
  requireds = (params&.requireds || []).map(&:name)
43
- optionals = params&.optionals || []
44
47
  keywords = (params&.keywords || []).map do |kw|
45
- [kw.name, kw.value.slice.gsub(/^_\./, '$.')]
48
+ [kw.name, kw.value.slice.gsub(/^_\./, "$.")]
46
49
  end.to_h
47
50
 
48
51
  [requireds.length, keywords]
49
- rescue
50
- [ proc.parameters.first&.length || 0, {}]
52
+ rescue StandardError
53
+ [proc.parameters.select { |p| p == :req }&.length || 0, {}]
51
54
  end
52
55
  end
53
56
  end
@@ -5,10 +5,18 @@ module Itsi
5
5
  class Server
6
6
  module TypedHandlers
7
7
  def self.handler_for(proc, input_schema)
8
- input_schema = proc.binding.eval(input_schema) if input_schema
9
- lambda do |req|
10
- req.params(input_schema) do |params|
11
- proc.call(req, params: params)
8
+ if input_schema
9
+ input_schema = proc.binding.eval(input_schema)
10
+ lambda do |req|
11
+ req.params(input_schema) do |params|
12
+ proc.call(req, params: params)
13
+ end
14
+ end
15
+ else
16
+ lambda do |req|
17
+ req.params do |params|
18
+ proc.call(req, params)
19
+ end
12
20
  end
13
21
  end
14
22
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.1.20"
5
+ VERSION = "0.2.3"
6
6
  end
7
7
  end
@@ -17,6 +17,9 @@ require_relative "http_response"
17
17
  require_relative "passfile"
18
18
  require_relative "../shell_completions/completions"
19
19
 
20
+ require "securerandom"
21
+ require "fileutils"
22
+
20
23
  module Itsi
21
24
  class Server
22
25
  extend RackInterface
@@ -24,21 +27,25 @@ module Itsi
24
27
  extend RouteTester
25
28
 
26
29
  class << self
27
-
28
30
  def running?
29
- !!@running
31
+ @running && !@running.empty?
30
32
  end
31
33
 
32
34
  def start_in_background_thread(cli_params = {}, &blk)
33
- @background_thread = start(cli_params, background: true, &blk)
35
+ @background_threads ||= []
36
+ server, background_thread = start(cli_params, background: true, &blk)
37
+ @background_threads << background_thread
38
+ server
34
39
  end
35
40
 
36
41
  def start(cli_params, background: false, &blk)
37
42
  itsi_file = Itsi::Server::Config.config_file_path(cli_params[:config_file])
43
+ Itsi.log_debug "Constructing server #{cli_params}: #{itsi_file}"
38
44
  server = new(cli_params, itsi_file, blk)
39
45
  previous_handler = Signal.trap(:INT, :DEFAULT)
40
46
  run = lambda do
41
- @running = server
47
+ @running ||= []
48
+ @running << server
42
49
 
43
50
  if cli_params[:daemonize]
44
51
  Itsi.log_info("Itsi is running in the background. Writing pid to #{Itsi::Server::Config.pid_file_path}")
@@ -47,13 +54,15 @@ module Itsi
47
54
  end
48
55
  write_pid
49
56
 
57
+ Itsi.log_info "Starting Itsi..."
50
58
  server.start
51
- @running = nil
59
+ @running.delete(server)
52
60
  Signal.trap(:INT, previous_handler)
53
61
  server
54
62
  end
55
- background ? Thread.new(&run) : run[]
56
- rescue
63
+ background ? [server, Thread.new(&run)] : run[]
64
+ rescue Exception => e # rubocop:disable Lint/RescueException
65
+ Itsi.log_error e.message
57
66
  end
58
67
 
59
68
  def static(cli_params)
@@ -61,7 +70,8 @@ module Itsi
61
70
  end
62
71
 
63
72
  def stop
64
- return unless pid = get_pid
73
+ return unless (pid = get_pid)
74
+
65
75
  Process.kill(:INT, pid)
66
76
  i = 0
67
77
  while i < 10
@@ -74,16 +84,18 @@ module Itsi
74
84
  end
75
85
  end
76
86
 
77
- def stop_background_thread
78
- @running&.stop
79
- @background_thread&.join
87
+ def stop_background_threads
88
+ @running && @running.each(&:stop)
89
+ @background_threads&.each(&:join)
90
+ @background_threads = []
91
+ @running = []
80
92
  end
81
93
 
82
94
  def write_pid
83
95
  File.write(Itsi::Server::Config.pid_file_path, Process.pid)
84
96
  end
85
97
 
86
- def get_pid(warn=true)
98
+ def get_pid(warn = true)
87
99
  pid = File.read(Itsi::Server::Config.pid_file_path).to_i
88
100
  if Process.kill(0, pid)
89
101
  pid
@@ -97,7 +109,7 @@ module Itsi
97
109
  end
98
110
 
99
111
  def test
100
- Itsi::Server::Config.test!(cli_params = {})
112
+ Itsi::Server::Config.test!({})
101
113
  end
102
114
 
103
115
  def init
@@ -105,25 +117,25 @@ module Itsi
105
117
  end
106
118
 
107
119
  def reload
108
- return unless pid = get_pid
120
+ return unless (pid = get_pid)
109
121
 
110
122
  Process.kill(:HUP, pid)
111
123
  end
112
124
 
113
125
  def restart
114
- return unless pid = get_pid
126
+ return unless (pid = get_pid)
115
127
 
116
128
  Process.kill(:USR1, pid)
117
129
  end
118
130
 
119
131
  def passfile(options, subcmd)
120
132
  filename = options[:passfile]
121
- unless filename
133
+ unless filename || subcmd == "echo"
122
134
  puts "Error: passfile not set. Use --passfile option to provide a path to a file containing hashed credentials."
123
135
  puts "This file contains hashed credentials and should not be included in source control without additional protection."
124
136
  exit(1)
125
137
  end
126
- algorithm = options.fetch(:algorithm, 'sha256')
138
+ algorithm = options.fetch(:algorithm, "sha256")
127
139
 
128
140
  unless %w[sha256 sha512 bcrypt argon2 none].include?(algorithm)
129
141
  puts "Invalid algorithm"
@@ -131,9 +143,9 @@ module Itsi
131
143
  end
132
144
 
133
145
  case subcmd
134
- when 'add', 'echo'
146
+ when "add", "echo"
135
147
  Passfile.send(subcmd, filename, algorithm)
136
- when 'remove', 'list'
148
+ when "remove", "list"
137
149
  Passfile.send(subcmd, filename)
138
150
  else
139
151
  puts "Valid subcommands are: add | remove | list"
@@ -141,31 +153,104 @@ module Itsi
141
153
  end
142
154
  end
143
155
 
156
+ def unique_path(dir, filename)
157
+ base = File.basename(filename, ".*")
158
+ ext = File.extname(filename)
159
+ candidate = File.join(dir, filename)
160
+ return candidate unless File.exist?(candidate)
161
+
162
+ i = 1
163
+ loop do
164
+ new_name = "#{base}_#{i}#{ext}"
165
+ candidate = File.join(dir, new_name)
166
+ return candidate unless File.exist?(candidate)
167
+
168
+ i += 1
169
+ end
170
+ end
171
+
172
+ def save_or_print(filename, content, options)
173
+ if options[:save_dir]
174
+ FileUtils.mkdir_p(options[:save_dir])
175
+ path = unique_path(options[:save_dir], filename)
176
+ File.write(path, content)
177
+ puts "Written to #{path}"
178
+ else
179
+ puts content
180
+ end
181
+ end
182
+
183
+ def secret(options)
184
+ require "openssl"
185
+ require "base64"
186
+
187
+ puts "Enter algorithm (one of: HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384):"
188
+ alg = $stdin.gets.chomp.upcase
189
+
190
+ case alg
191
+ when /^HS(\d+)$/
192
+ bits = ::Regexp.last_match(1).to_i
193
+ bytes = bits / 8
194
+ key = SecureRandom.random_bytes(bytes)
195
+ pem = Base64.strict_encode64(key)
196
+ content = "=== HMAC #{bits}-bit Secret (base64) ===\n#{pem}\n"
197
+ save_or_print("hmac_#{bits}_secret.txt", content, options)
198
+
199
+ when /^RS/, /^PS/
200
+ rsa = OpenSSL::PKey::RSA.new(2048)
201
+ priv = rsa.to_pem
202
+ pub = rsa.public_key.to_pem
203
+ save_or_print("rsa_private.pem", "=== RSA Private Key ===\n#{priv}", options)
204
+ save_or_print("rsa_public.pem", "=== RSA Public Key ===\n#{pub}", options)
205
+
206
+ when "ES256", "ES384"
207
+ curve = (alg == "ES256" ? "prime256v1" : "secp384r1")
208
+ ec = OpenSSL::PKey::EC.new(curve)
209
+ ec.generate_key
210
+ priv = ec.to_pem
211
+ pub_ec = ec.dup
212
+ pub_ec.private_key = nil
213
+ pub = pub_ec.to_pem
214
+ save_or_print("ecdsa_private.pem", "=== ECDSA Private Key ===\n#{priv}", options)
215
+ save_or_print("ecdsa_public.pem", "=== ECDSA Public Key ===\n#{pub}", options)
216
+
217
+ else
218
+ warn "Unsupported algorithm: #{alg}"
219
+ exit 1
220
+ end
221
+ end
222
+
144
223
  def add_worker
145
- return unless pid = get_pid
224
+ return unless (pid = get_pid)
146
225
 
147
226
  Process.kill(:TTIN, pid)
148
227
  end
149
228
 
150
229
  def remove_worker
151
- return unless pid = get_pid
230
+ return unless (pid = get_pid)
152
231
 
153
232
  Process.kill(:TTOU, pid)
154
233
  end
155
234
 
156
235
  def status
157
- return unless pid = get_pid
236
+ return unless (pid = get_pid)
237
+
158
238
  Itsi.log_info("Itsi running on #{pid}")
159
239
  Process.kill(:USR2, pid)
160
240
  end
161
241
 
162
242
  def load_route_middleware_stack(cli_params)
163
- Config.build_config(cli_params, Itsi::Server::Config.config_file_path(cli_params[:config_file_path]))[
164
- "middleware_loader"
165
- ][]
243
+ middleware, errors = Config.build_config(cli_params,
244
+ Itsi::Server::Config.config_file_path(cli_params[:config_file_path]))
245
+ if errors.any?
246
+ puts errors
247
+ []
248
+ else
249
+ middleware["middleware_loader"][]
250
+ end
166
251
  end
167
252
 
168
- def test_route(cli_params = {}, route_str)
253
+ def test_route(cli_params, route_str)
169
254
  matched_route = load_route_middleware_stack(cli_params).find do |route|
170
255
  route["route"] =~ route_str
171
256
  end
@@ -187,7 +272,6 @@ module Itsi
187
272
  end
188
273
 
189
274
  alias serve start
190
-
191
275
  end
192
276
  end
193
277
  end