itsi 0.1.20 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -8
- data/Cargo.lock +2 -2
- data/LICENSE.txt +698 -0
- data/README.md +15 -4
- data/Rakefile +9 -5
- data/crates/itsi_acme/.gitignore +4 -0
- data/crates/itsi_acme/Cargo.toml +86 -0
- data/crates/itsi_acme/LICENSE-APACHE +201 -0
- data/crates/itsi_acme/LICENSE-MIT +23 -0
- data/crates/itsi_acme/README.md +9 -0
- data/crates/itsi_acme/examples/high_level.rs +63 -0
- data/crates/itsi_acme/examples/high_level_warp.rs +52 -0
- data/crates/itsi_acme/examples/low_level.rs +87 -0
- data/crates/itsi_acme/examples/low_level_axum.rs +66 -0
- data/crates/itsi_acme/src/acceptor.rs +81 -0
- data/crates/itsi_acme/src/acme.rs +354 -0
- data/crates/itsi_acme/src/axum.rs +86 -0
- data/crates/itsi_acme/src/cache.rs +39 -0
- data/crates/itsi_acme/src/caches/boxed.rs +80 -0
- data/crates/itsi_acme/src/caches/composite.rs +69 -0
- data/crates/itsi_acme/src/caches/dir.rs +106 -0
- data/crates/itsi_acme/src/caches/mod.rs +11 -0
- data/crates/itsi_acme/src/caches/no.rs +78 -0
- data/crates/itsi_acme/src/caches/test.rs +136 -0
- data/crates/itsi_acme/src/config.rs +172 -0
- data/crates/itsi_acme/src/https_helper.rs +69 -0
- data/crates/itsi_acme/src/incoming.rs +142 -0
- data/crates/itsi_acme/src/jose.rs +161 -0
- data/crates/itsi_acme/src/lib.rs +142 -0
- data/crates/itsi_acme/src/resolver.rs +59 -0
- data/crates/itsi_acme/src/state.rs +424 -0
- data/crates/itsi_rb_helpers/src/lib.rs +4 -3
- data/crates/itsi_scheduler/Cargo.toml +1 -1
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +8 -2
- data/crates/itsi_scheduler/src/lib.rs +1 -0
- data/crates/itsi_server/Cargo.toml +1 -1
- data/crates/itsi_server/src/lib.rs +2 -1
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +18 -1
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +11 -3
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +122 -63
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +2 -0
- data/crates/itsi_server/src/server/binds/bind.rs +3 -0
- data/crates/itsi_server/src/server/binds/listener.rs +12 -5
- data/crates/itsi_server/src/server/binds/tls.rs +13 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +12 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +8 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +9 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +48 -43
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +11 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +39 -12
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +36 -27
- data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +25 -11
- data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +12 -3
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +74 -72
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +15 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +11 -8
- data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +19 -11
- data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +5 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +2 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +11 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +17 -20
- data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +19 -8
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +16 -37
- data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +22 -12
- data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +26 -11
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +7 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +14 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +19 -0
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +49 -13
- data/crates/itsi_server/src/server/mod.rs +1 -0
- data/crates/itsi_server/src/server/redirect_type.rs +26 -0
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +22 -16
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +49 -12
- data/crates/itsi_server/src/server/signal.rs +1 -0
- data/crates/itsi_server/src/server/size_limited_incoming.rs +6 -0
- data/crates/itsi_server/src/server/thread_worker.rs +5 -1
- data/crates/itsi_server/src/services/itsi_http_service.rs +20 -2
- data/crates/itsi_server/src/services/rate_limiter.rs +15 -4
- data/crates/itsi_server/src/services/static_file_server.rs +33 -19
- data/crates/itsi_tracing/src/lib.rs +42 -22
- data/docs/content/_index.md +1 -2
- data/docs/content/acknowledgements/_index.md +5 -2
- data/docs/content/configuration/_index.md +8 -5
- data/docs/content/contact/_index.md +8 -1
- data/docs/content/faqs/_index.md +5 -3
- data/docs/content/features/_index.md +56 -50
- data/docs/content/getting_started/_index.md +8 -5
- data/docs/content/getting_started/local_development.md +68 -8
- data/docs/content/getting_started/logging.md +16 -9
- data/docs/content/getting_started/running_itsi_in_production.md +5 -3
- data/docs/content/getting_started/signals.md +38 -0
- data/docs/content/itsi_scheduler/_index.md +8 -7
- data/docs/content/utilities/_index.md +13 -0
- data/docs/content/utilities/config_file_testing.md +17 -0
- data/docs/content/utilities/passfile_generator.md +41 -0
- data/docs/content/utilities/route_testing.md +27 -0
- data/docs/content/utilities/secrets_management.md +30 -0
- data/docs/hugo.yaml +1 -1
- data/fairytale.txt +3 -4
- data/gems/scheduler/Cargo.lock +1 -1
- data/gems/scheduler/README.md +4 -5
- data/gems/scheduler/Rakefile +0 -4
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
- data/gems/scheduler/test/test_active_record.rb +12 -7
- data/gems/server/Cargo.lock +1 -1
- data/gems/server/Rakefile +0 -4
- data/gems/server/exe/itsi +13 -2
- data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
- data/gems/server/lib/itsi/http_request.rb +40 -9
- data/gems/server/lib/itsi/http_response.rb +2 -1
- data/gems/server/lib/itsi/passfile.rb +0 -1
- data/gems/server/lib/itsi/server/config/config_helpers.rb +20 -8
- data/gems/server/lib/itsi/server/config/dsl.rb +20 -435
- data/gems/server/lib/itsi/server/config/known_paths.rb +4 -1
- data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
- data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
- data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
- data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
- data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
- data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
- data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
- data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
- data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
- data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
- data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
- data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
- data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
- data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
- data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
- data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
- data/gems/server/lib/itsi/server/config/middleware/error_response.md +61 -0
- data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
- data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
- data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
- data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
- data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
- data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
- data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
- data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
- data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
- data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
- data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
- data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
- data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
- data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
- data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
- data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
- data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
- data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
- data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
- data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
- data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
- data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
- data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
- data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
- data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
- data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
- data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
- data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
- data/gems/server/lib/itsi/server/config/middleware/static_assets.md +73 -0
- data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
- data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
- data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
- data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
- data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
- data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
- data/gems/server/lib/itsi/server/config/option.rb +5 -0
- data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
- data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
- data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
- data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
- data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
- data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
- data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
- data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
- data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
- data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
- data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
- data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
- data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/include.md +20 -0
- data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
- data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
- data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
- data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
- data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
- data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
- data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
- data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
- data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
- data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
- data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
- data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
- data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
- data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
- data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
- data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
- data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
- data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
- data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
- data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
- data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
- data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
- data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
- data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
- data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
- data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
- data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
- data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
- data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
- data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
- data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
- data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
- data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
- data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
- data/gems/server/lib/itsi/server/config/typed_struct.rb +59 -20
- data/gems/server/lib/itsi/server/config.rb +77 -48
- data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
- data/gems/server/lib/itsi/server/grpc/grpc_call.rb +1 -1
- data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +11 -4
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -3
- data/gems/server/lib/itsi/server/route_tester.rb +58 -8
- data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
- data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
- data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +5 -4
- data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +98 -14
- data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
- data/gems/server/test/helpers/test_helper.rb +89 -29
- data/gems/server/test/middleware/allow_list.rb +128 -0
- data/gems/server/test/middleware/auth_api_key.rb +141 -0
- data/gems/server/test/middleware/auth_basic.rb +91 -0
- data/gems/server/test/middleware/auth_jwt.rb +214 -0
- data/gems/server/test/middleware/cache_control.rb +82 -0
- data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
- data/gems/server/test/middleware/compression.rb +89 -0
- data/gems/server/test/middleware/cors.rb +113 -0
- data/gems/server/test/middleware/csp.rb +62 -0
- data/gems/server/test/middleware/deny_list.rb +131 -0
- data/gems/server/test/middleware/endpoint.rb +300 -0
- data/gems/server/test/middleware/etag.rb +75 -0
- data/gems/server/test/middleware/grpc/grpc.rb +158 -0
- data/gems/server/test/middleware/grpc/test_service.proto +32 -0
- data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
- data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
- data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
- data/gems/server/test/middleware/header_interpolation.rb +35 -0
- data/gems/server/test/middleware/intrusion_protection.rb +259 -0
- data/gems/server/test/middleware/location.rb +220 -0
- data/gems/server/test/middleware/max_body.rb +20 -0
- data/gems/server/test/middleware/proxy.rb +415 -0
- data/gems/server/test/middleware/rate_limit.rb +211 -0
- data/gems/server/test/middleware/redirect.rb +85 -0
- data/gems/server/test/middleware/request_headers.rb +50 -0
- data/gems/server/test/middleware/response_headers.rb +50 -0
- data/gems/server/test/middleware/static_assets.rb +374 -0
- data/gems/server/test/middleware/static_response.rb +56 -0
- data/gems/server/test/middleware/string_rewrite.rb +112 -0
- data/gems/server/test/options/bind.rb +47 -0
- data/gems/server/test/options/header_read_timeout.rb +23 -0
- data/gems/server/test/options/test_request_timeout.rb +16 -0
- data/gems/server/test/options/test_workers.rb +2 -4
- data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
- data/grpc_test/Itsi.rb +11 -0
- data/grpc_test/echo.proto +14 -0
- data/grpc_test/echo_pb.rb +16 -0
- data/grpc_test/echo_service_impl.rb +8 -0
- data/grpc_test/echo_services_pb.rb +22 -0
- data/lib/itsi/version.rb +1 -1
- data/tasks.txt +15 -72
- metadata +210 -7
- data/gems/server/lib/itsi/server/default_config/Itsi-rackup.rb +0 -119
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'test_service_services_pb' # generated by grpc_tools_ruby_protoc
|
2
|
+
|
3
|
+
class TestServiceImpl < Test::TestService::Service
|
4
|
+
# Unary: echo back the message [oai_citation_attribution:2‡Gustavo Caso](https://gustavocaso.dev/posts/grpc-tutorial-with-ruby/)
|
5
|
+
def unary_echo(req, _unused_call)
|
6
|
+
Test::EchoResponse.new(message: req.message)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Client‑streaming: collect all incoming messages into one response [oai_citation_attribution:3‡Medium](https://alessiobussolari.medium.com/integrating-grpc-with-ruby-on-rails-2b2a203107d5?utm_source=chatgpt.com)
|
10
|
+
def client_stream(stream, _call)
|
11
|
+
msgs = stream.map(&:message)
|
12
|
+
Test::StreamResponse.new(messages: msgs)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Server‑streaming: send back one StreamResponse per incoming request, with each char [oai_citation_attribution:4‡Medium](https://alessiobussolari.medium.com/integrating-grpc-with-ruby-on-rails-2b2a203107d5?utm_source=chatgpt.com)
|
16
|
+
def server_stream(req, _call)
|
17
|
+
Enumerator.new do |y|
|
18
|
+
req.message.each_char { |c| y << Test::StreamResponse.new(messages: [c]) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Bidirectional: for each incoming EchoRequest, immediately echo it back [oai_citation_attribution:5‡Medium](https://alessiobussolari.medium.com/integrating-grpc-with-ruby-on-rails-2b2a203107d5?utm_source=chatgpt.com)
|
23
|
+
def bidi_stream(stream, _call)
|
24
|
+
Enumerator.new do |y|
|
25
|
+
stream.each { |req| y << Test::EchoResponse.new(message: req.message.upcase) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
3
|
+
# source: test_service.proto
|
4
|
+
|
5
|
+
require 'google/protobuf'
|
6
|
+
|
7
|
+
|
8
|
+
descriptor_data = "\n\x12test_service.proto\x12\x04test\"\x1e\n\x0b\x45\x63hoRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\"\x1f\n\x0c\x45\x63hoResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\" \n\rStreamRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\"\"\n\x0eStreamResponse\x12\x10\n\x08messages\x18\x01 \x03(\t2\xfa\x01\n\x0bTestService\x12\x34\n\tUnaryEcho\x12\x11.test.EchoRequest\x1a\x12.test.EchoResponse\"\x00\x12=\n\x0c\x43lientStream\x12\x13.test.StreamRequest\x1a\x14.test.StreamResponse\"\x00(\x01\x12;\n\x0cServerStream\x12\x11.test.EchoRequest\x1a\x14.test.StreamResponse\"\x00\x30\x01\x12\x39\n\nBidiStream\x12\x11.test.EchoRequest\x1a\x12.test.EchoResponse\"\x00(\x01\x30\x01\x62\x06proto3"
|
9
|
+
|
10
|
+
pool = Google::Protobuf::DescriptorPool.generated_pool
|
11
|
+
pool.add_serialized_file(descriptor_data)
|
12
|
+
|
13
|
+
module Test
|
14
|
+
EchoRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("test.EchoRequest").msgclass
|
15
|
+
EchoResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("test.EchoResponse").msgclass
|
16
|
+
StreamRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("test.StreamRequest").msgclass
|
17
|
+
StreamResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("test.StreamResponse").msgclass
|
18
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# Source: test_service.proto for package 'test'
|
3
|
+
|
4
|
+
require 'grpc'
|
5
|
+
require_relative 'test_service_pb'
|
6
|
+
|
7
|
+
module Test
|
8
|
+
module TestService
|
9
|
+
# A service with one of each RPC type [oai_citation_attribution:0‡Википедия — свободная энциклопедия](https://ru.wikipedia.org/wiki/GRPC?utm_source=chatgpt.com)
|
10
|
+
class Service
|
11
|
+
|
12
|
+
include ::GRPC::GenericService
|
13
|
+
|
14
|
+
self.marshal_class_method = :encode
|
15
|
+
self.unmarshal_class_method = :decode
|
16
|
+
self.service_name = 'test.TestService'
|
17
|
+
|
18
|
+
# Unary RPC
|
19
|
+
rpc :UnaryEcho, ::Test::EchoRequest, ::Test::EchoResponse
|
20
|
+
# Client‑streaming RPC
|
21
|
+
rpc :ClientStream, stream(::Test::StreamRequest), ::Test::StreamResponse
|
22
|
+
# Server‑streaming RPC
|
23
|
+
rpc :ServerStream, ::Test::EchoRequest, stream(::Test::StreamResponse)
|
24
|
+
# Bidirectional streaming RPC
|
25
|
+
rpc :BidiStream, stream(::Test::EchoRequest), stream(::Test::EchoResponse)
|
26
|
+
end
|
27
|
+
|
28
|
+
Stub = Service.rpc_stub_class
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class TestHeaderInterpolation < Minitest::Test
|
2
|
+
def test_request_header_interpolation_and_response_header_interpolation
|
3
|
+
server(
|
4
|
+
itsi_rb: lambda do
|
5
|
+
# 1) Echo back incoming header "X-Auth-User" in a new request header "X-User"
|
6
|
+
request_headers \
|
7
|
+
additions: { "X-User" => ["{X-Auth-User}"] },
|
8
|
+
removals: []
|
9
|
+
|
10
|
+
# 2) After the handler runs, take the request‑echoed "X-User" and also echo it into "X-User-Echo" in the response
|
11
|
+
response_headers \
|
12
|
+
additions: { "X-User-Echo" => ["{X-User}"] },
|
13
|
+
removals: []
|
14
|
+
|
15
|
+
get("/echo") do |r|
|
16
|
+
# Return the value of the new X-User header in the body for verification
|
17
|
+
r.ok("Hello #{r.header("X-User").first}", headers: {"X-User" => [r.header("X-User").first]})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
) do
|
21
|
+
# Simulate a client sending X-Auth-User: alice
|
22
|
+
req_headers = { "X-Auth-User" => "alice" }
|
23
|
+
|
24
|
+
res = get_resp("/echo", req_headers)
|
25
|
+
assert_equal "200", res.code
|
26
|
+
|
27
|
+
# The request middleware should have created X-User == "alice",
|
28
|
+
# and the handler returned it in the body:
|
29
|
+
assert_equal "Hello alice", res.body
|
30
|
+
|
31
|
+
# The response middleware should have added X-User-Echo == "alice"
|
32
|
+
assert_equal "alice", res["X-User-Echo"]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require_relative "../helpers/test_helper"
|
2
|
+
require "redis"
|
3
|
+
|
4
|
+
class TestIntrusionProtection < Minitest::Test
|
5
|
+
|
6
|
+
# 1. Banned URL pattern causes immediate ban + 403
|
7
|
+
def test_banned_url_pattern
|
8
|
+
server(
|
9
|
+
itsi_rb: lambda do
|
10
|
+
location "/secret" do
|
11
|
+
intrusion_protection \
|
12
|
+
banned_url_patterns: ["/secret"],
|
13
|
+
banned_time_seconds: 0.1,
|
14
|
+
store_config: { redis: { connection_url: "redis://localhost:6379/14" } }
|
15
|
+
get { |r| r.ok "never" }
|
16
|
+
end
|
17
|
+
location "/public" do
|
18
|
+
get { |r| r.ok "public" }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
) do
|
22
|
+
|
23
|
+
# First request: matched → banned + forbidden
|
24
|
+
res1 = get_resp("/secret")
|
25
|
+
assert_equal "403", res1.code
|
26
|
+
|
27
|
+
# Immediately banned: second request also forbidden
|
28
|
+
res2 = get_resp("/secret")
|
29
|
+
assert_equal "403", res2.code
|
30
|
+
|
31
|
+
# After ban TTL expires
|
32
|
+
sleep 0.11
|
33
|
+
|
34
|
+
res3 = get_resp("/public")
|
35
|
+
assert_equal "200", res3.code
|
36
|
+
res4 = get_resp("/secret")
|
37
|
+
assert_equal "403", res4.code
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# 2. Banned header pattern causes ban + 403
|
42
|
+
def test_banned_header_pattern
|
43
|
+
server(
|
44
|
+
itsi_rb: lambda do
|
45
|
+
intrusion_protection \
|
46
|
+
banned_header_patterns: { "User-Agent" => ["BadBot"] },
|
47
|
+
banned_time_seconds: 0.1,
|
48
|
+
store_config: "in_memory"
|
49
|
+
get("/hi") { |r| r.ok "hi" }
|
50
|
+
end
|
51
|
+
) do
|
52
|
+
# First: header matches → banned
|
53
|
+
res1 = get_resp("/hi", { "User-Agent" => "MyBadBot/1.0" })
|
54
|
+
assert_equal "403", res1.code
|
55
|
+
|
56
|
+
# Still banned until TTL
|
57
|
+
res2 = get_resp("/hi", { "User-Agent" => "MyBadBot/1.0" })
|
58
|
+
assert_equal "403", res2.code
|
59
|
+
|
60
|
+
sleep 0.11
|
61
|
+
# After TTL, banned set clears; header still matches → ban again
|
62
|
+
res3 = get_resp("/hi", { "User-Agent" => "MyBadBot/1.0" })
|
63
|
+
assert_equal "403", res3.code
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# 3. Clean traffic passes through
|
68
|
+
def test_clean_traffic
|
69
|
+
server(
|
70
|
+
itsi_rb: lambda do
|
71
|
+
intrusion_protection \
|
72
|
+
banned_url_patterns: [".*\\.php$"],
|
73
|
+
banned_header_patterns: { "X-Test" => ["evil"] },
|
74
|
+
banned_time_seconds: 0.1,
|
75
|
+
store_config: { redis: { connection_url: "redis://localhost:6379/13" } }
|
76
|
+
get("/hello") { |r| r.ok "world" }
|
77
|
+
end
|
78
|
+
) do
|
79
|
+
# Non‑matching URL & header → allowed
|
80
|
+
res = get_resp("/hello", { "User-Agent" => "Mozilla/5.0" })
|
81
|
+
assert_equal "200", res.code
|
82
|
+
assert_equal "world", res.body
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# 4. Custom error_response
|
87
|
+
def test_custom_error_response
|
88
|
+
server(
|
89
|
+
itsi_rb: lambda do
|
90
|
+
intrusion_protection \
|
91
|
+
banned_url_patterns: ["/bad"],
|
92
|
+
banned_header_patterns: {},
|
93
|
+
banned_time_seconds: 5,
|
94
|
+
store_config: "in_memory",
|
95
|
+
error_response: {
|
96
|
+
code: 401,
|
97
|
+
plaintext: { inline: "Halt!" },
|
98
|
+
default: "plaintext"
|
99
|
+
}
|
100
|
+
get("/bad") { |r| r.ok "never" }
|
101
|
+
end
|
102
|
+
) do
|
103
|
+
res = get_resp("/bad")
|
104
|
+
assert_equal "401", res.code
|
105
|
+
assert_equal "Halt!", res.body
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# 5. Intrusion protection middleware stacks (nested: parent + child)
|
110
|
+
def test_nested_intrusion_protection_stacking
|
111
|
+
server(
|
112
|
+
itsi_rb: lambda do
|
113
|
+
location "/protected" do
|
114
|
+
intrusion_protection \
|
115
|
+
banned_url_patterns: ["/nested"],
|
116
|
+
banned_time_seconds: 0.1,
|
117
|
+
store_config: "in_memory"
|
118
|
+
|
119
|
+
location "/nested" do
|
120
|
+
intrusion_protection \
|
121
|
+
banned_header_patterns: { "X-Evil" => ["1"] },
|
122
|
+
banned_time_seconds: 0.1,
|
123
|
+
store_config: "in_memory"
|
124
|
+
get { |r| r.ok "should not see this" }
|
125
|
+
end
|
126
|
+
|
127
|
+
get { |r| r.ok "Should also not see this" }
|
128
|
+
end
|
129
|
+
location "/public" do
|
130
|
+
get { |r| r.ok "safe" }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
) do
|
134
|
+
# 1. Triggers child (header) rule → ban
|
135
|
+
res1 = get_resp("protected/nested", { "X-Evil" => "1" })
|
136
|
+
assert_equal "403", res1.code
|
137
|
+
|
138
|
+
sleep 0.11
|
139
|
+
|
140
|
+
# 3. Triggers parent (path) rule → ban
|
141
|
+
res3 = get_resp("protected/nested")
|
142
|
+
assert_equal "403", res3.code
|
143
|
+
|
144
|
+
sleep 0.11
|
145
|
+
|
146
|
+
# 4. Confirm public route works
|
147
|
+
res4 = get_resp("/public")
|
148
|
+
assert_equal "200", res4.code
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# 6. Sibling intrusion protection rules stack independently
|
153
|
+
def test_sibling_intrusion_protection_stacking
|
154
|
+
server(
|
155
|
+
itsi_rb: lambda do
|
156
|
+
location "/one" do
|
157
|
+
intrusion_protection \
|
158
|
+
banned_url_patterns: ["/one"],
|
159
|
+
banned_time_seconds: 0.1,
|
160
|
+
store_config: "in_memory"
|
161
|
+
get { |r| r.ok "never" }
|
162
|
+
end
|
163
|
+
|
164
|
+
location "/two" do
|
165
|
+
intrusion_protection \
|
166
|
+
banned_header_patterns: { "X-Bot" => ["true"] },
|
167
|
+
banned_time_seconds: 0.1,
|
168
|
+
store_config: "in_memory"
|
169
|
+
get { |r| r.ok "never" }
|
170
|
+
end
|
171
|
+
|
172
|
+
location "/ok" do
|
173
|
+
get { |r| r.ok "ok" }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
) do
|
177
|
+
# Route `/one` banned by path
|
178
|
+
res1 = get_resp("/one")
|
179
|
+
assert_equal "403", res1.code
|
180
|
+
|
181
|
+
# Route `/two` banned by header
|
182
|
+
res2 = get_resp("/two", { "X-Bot" => "true" })
|
183
|
+
assert_equal "403", res2.code
|
184
|
+
|
185
|
+
# Route `/ok` untouched
|
186
|
+
res3 = get_resp("/ok")
|
187
|
+
assert_equal "200", res3.code
|
188
|
+
|
189
|
+
# Wait for TTL to expire, confirm unban
|
190
|
+
sleep 0.11
|
191
|
+
|
192
|
+
# `/one` still banned due to path match → re-ban
|
193
|
+
res4 = get_resp("/one")
|
194
|
+
assert_equal "403", res4.code
|
195
|
+
|
196
|
+
# `/two` still banned by header → re-ban
|
197
|
+
res5 = get_resp("/two", { "X-Bot" => "true" })
|
198
|
+
assert_equal "403", res5.code
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# 7. Trusted proxy: bans applied based on forwarded IP
|
203
|
+
def test_trusted_proxy_bans_based_on_forwarded_ip
|
204
|
+
server(
|
205
|
+
itsi_rb: lambda do
|
206
|
+
intrusion_protection \
|
207
|
+
banned_url_patterns: ["/flagged"],
|
208
|
+
banned_time_seconds: 0.1,
|
209
|
+
trusted_proxies: {
|
210
|
+
"127.0.0.1" => { header: { name: "X-Forwarded-For" } }
|
211
|
+
},
|
212
|
+
store_config: "in_memory"
|
213
|
+
get("/flagged") { |r| r.ok "should not see" }
|
214
|
+
get("/okay") { |r| r.ok "ok" }
|
215
|
+
end
|
216
|
+
) do
|
217
|
+
# Request with client IP 203.0.113.42 via trusted proxy → triggers ban
|
218
|
+
res1 = get_resp("/flagged", { "X-Forwarded-For" => "203.0.113.42" })
|
219
|
+
assert_equal "403", res1.code
|
220
|
+
|
221
|
+
# Second request (same IP) still banned
|
222
|
+
res2 = get_resp("/flagged", { "X-Forwarded-For" => "203.0.113.42" })
|
223
|
+
assert_equal "403", res2.code
|
224
|
+
|
225
|
+
# Different client IP → not banned
|
226
|
+
res3 = get_resp("/flagged", { "X-Forwarded-For" => "203.0.113.99" })
|
227
|
+
assert_equal "403", res3.code # path still matches; gets banned
|
228
|
+
|
229
|
+
# Wait for first ban to expire
|
230
|
+
sleep 0.11
|
231
|
+
res4 = get_resp("/okay", { "X-Forwarded-For" => "203.0.113.42" })
|
232
|
+
assert_equal "200", res4.code
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# 8. Untrusted proxy: forwarded IP ignored; ban keyed by socket IP
|
237
|
+
def test_untrusted_proxy_ignores_forwarded_ip
|
238
|
+
server(
|
239
|
+
itsi_rb: lambda do
|
240
|
+
intrusion_protection \
|
241
|
+
banned_url_patterns: ["/banned"],
|
242
|
+
banned_time_seconds: 0.1,
|
243
|
+
trusted_proxies: {
|
244
|
+
"10.0.0.1" => { header: { name: "X-Forwarded-For" } }
|
245
|
+
},
|
246
|
+
store_config: "in_memory"
|
247
|
+
get("/banned") { |r| r.ok "nope" }
|
248
|
+
end
|
249
|
+
) do
|
250
|
+
# This header is ignored (sender IP is not trusted)
|
251
|
+
res1 = get_resp("/banned", { "X-Forwarded-For" => "198.51.100.7" })
|
252
|
+
assert_equal "403", res1.code
|
253
|
+
|
254
|
+
# Banned again based on socket IP, not spoofed one
|
255
|
+
res2 = get_resp("/banned", { "X-Forwarded-For" => "198.51.100.99" })
|
256
|
+
assert_equal "403", res2.code
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require_relative "../helpers/test_helper"
|
2
|
+
|
3
|
+
class TestNestedLocation < Minitest::Test
|
4
|
+
# Tests that a nested block takes precedence over its parent block
|
5
|
+
def test_basic_nested_route
|
6
|
+
server(
|
7
|
+
itsi_rb: lambda do
|
8
|
+
# The outer location "/" applies to all routes.
|
9
|
+
location "/" do
|
10
|
+
# A nested location matching "/foo" with its own GET handler.
|
11
|
+
location "/foo" do
|
12
|
+
get("/bar") { |r| r.respond("From nested /foo/bar") }
|
13
|
+
end
|
14
|
+
# This GET handler is only hit if no nested location applies.
|
15
|
+
get("/bar") { |r| r.respond("From root /bar") }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
) do
|
19
|
+
res_nested = get_resp("/foo/bar")
|
20
|
+
assert_equal "From nested /foo/bar", res_nested.body
|
21
|
+
|
22
|
+
res_root = get_resp("/bar")
|
23
|
+
assert_equal "From root /bar", res_root.body
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Tests deep nesting where dynamic segments are used
|
28
|
+
def test_deeply_nested_route
|
29
|
+
server(
|
30
|
+
itsi_rb: lambda do
|
31
|
+
location "/v1" do
|
32
|
+
location "/users" do
|
33
|
+
# A nested capture for user id.
|
34
|
+
location "/:user_id" do
|
35
|
+
get("/profile") { |r, params| r.respond("User profile for v1/users/#{params[:user_id]}") }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
) do
|
41
|
+
res = get_resp("/v1/users/123/profile")
|
42
|
+
assert_equal "User profile for v1/users/123", res.body
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Tests that ordering of nested location blocks is honored. Only the first matching block should execute.
|
47
|
+
def test_nested_route_ordering
|
48
|
+
server(
|
49
|
+
itsi_rb: lambda do
|
50
|
+
location "/" do
|
51
|
+
location "/alpha" do
|
52
|
+
# This is the first nested match for "/alpha/beta"
|
53
|
+
location "/beta" do
|
54
|
+
get("/gamma") { |r| r.respond("Response from first nested /alpha/beta/gamma") }
|
55
|
+
end
|
56
|
+
# Though this block also matches "/alpha/beta", it should never be reached.
|
57
|
+
location "/beta" do
|
58
|
+
get("/gamma") { |r| r.respond("Response from second nested /alpha/beta/gamma") }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
# This handler is defined on the outer level and is a fallback.
|
62
|
+
get("/alpha/beta/gamma") { |r| r.respond("Response from outer /alpha/beta/gamma") }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
) do
|
66
|
+
res = get_resp("/alpha/beta/gamma")
|
67
|
+
# According to the recursive matching rules, the first matching child (/alpha then /beta) is used.
|
68
|
+
assert_equal "Response from first nested /alpha/beta/gamma", res.body
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Tests multiple nested levels under a common outer location.
|
73
|
+
def test_multiple_nested_options
|
74
|
+
server(
|
75
|
+
itsi_rb: lambda do
|
76
|
+
location "/api" do
|
77
|
+
get("/status") { |r| r.respond("API Status from /api") }
|
78
|
+
location "/users" do
|
79
|
+
get("/list") { |r| r.respond("User List from /api/users") }
|
80
|
+
location "/:user_id" do
|
81
|
+
get("/details") { |r| r.respond("User Details from /api/users/:user_id") }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
) do
|
87
|
+
res_status = get_resp("/api/status")
|
88
|
+
assert_equal "API Status from /api", res_status.body
|
89
|
+
|
90
|
+
res_list = get_resp("/api/users/list")
|
91
|
+
assert_equal "User List from /api/users", res_list.body
|
92
|
+
|
93
|
+
res_details = get_resp("/api/users/42/details")
|
94
|
+
assert_equal "User Details from /api/users/:user_id", res_details.body
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_dynamic_vs_wildcard_precedence
|
99
|
+
server(
|
100
|
+
itsi_rb: lambda do
|
101
|
+
location "/products" do
|
102
|
+
# This nested block should match when a numeric id is provided.
|
103
|
+
location "/:id([0-9]+)" do
|
104
|
+
get("/details") { |r| r.respond("Product details for numeric id") }
|
105
|
+
end
|
106
|
+
# Fallback handler for when the numeric match does not occur.
|
107
|
+
get("/details") { |r| r.respond("General product details") }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
) do
|
111
|
+
res_dynamic = get_resp("/products/123/details")
|
112
|
+
assert_equal "Product details for numeric id", res_dynamic.body
|
113
|
+
|
114
|
+
res_fallback = get_resp("/products/details")
|
115
|
+
assert_equal "General product details", res_fallback.body
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Test deep nesting with multiple dynamic segments (year, month, slug) versus a fallback archive route.
|
120
|
+
def test_multiple_dynamic_segments
|
121
|
+
server(
|
122
|
+
itsi_rb: lambda do
|
123
|
+
location "/blog" do
|
124
|
+
# Nested dynamic segments for a blog post.
|
125
|
+
location "/:year([0-9]{4})" do
|
126
|
+
location "/:month([0-9]{1,2})" do
|
127
|
+
location "/:slug" do
|
128
|
+
get { |r, params| r.respond("Blog post: #{params[:year]}/#{params[:month]}/#{params[:slug]}") }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
# Fallback route for blog archive.
|
133
|
+
get("/archive") { |r| r.respond("Blog archive") }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
) do
|
137
|
+
res_post = get_resp("/blog/2021/9/challenge-post")
|
138
|
+
assert_equal "Blog post: 2021/9/challenge-post", res_post.body
|
139
|
+
|
140
|
+
res_archive = get_resp("/blog/archive")
|
141
|
+
assert_equal "Blog archive", res_archive.body
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Test mixed static and dynamic segments. The static route should override the dynamic one for an exact match.
|
146
|
+
def test_static_vs_dynamic_precedence
|
147
|
+
server(
|
148
|
+
itsi_rb: lambda do
|
149
|
+
location "/dashboard" do
|
150
|
+
# Static route for settings.
|
151
|
+
location "/settings" do
|
152
|
+
get { |r| r.respond("Dashboard settings") }
|
153
|
+
end
|
154
|
+
# Dynamic route for other sections.
|
155
|
+
location "/:section" do
|
156
|
+
get { |r, params| r.respond("Dashboard section: #{params[:section]}") }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
) do
|
161
|
+
res_static = get_resp("/dashboard/settings")
|
162
|
+
assert_equal "Dashboard settings", res_static.body
|
163
|
+
|
164
|
+
res_dynamic = get_resp("/dashboard/stats")
|
165
|
+
assert_equal "Dashboard section: stats", res_dynamic.body
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Test overlapping regex-based routing versus a fallback dynamic match.
|
170
|
+
def test_overlapping_regex_and_fallback
|
171
|
+
server(
|
172
|
+
itsi_rb: lambda do
|
173
|
+
location "/files" do
|
174
|
+
# This nested location will match when the filename ends with .txt.
|
175
|
+
location "/:filename([a-z]+\.txt)" do
|
176
|
+
get { |r, params| r.respond("Text file: #{params[:filename]}") }
|
177
|
+
end
|
178
|
+
# Fallback for any file that doesn't match the above regex.
|
179
|
+
location "/:filename" do
|
180
|
+
get { |r, params| r.respond("Other file: #{params[:filename]}") }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
) do
|
185
|
+
res_txt = get_resp("/files/readme.txt")
|
186
|
+
assert_equal "Text file: readme.txt", res_txt.body
|
187
|
+
|
188
|
+
res_other = get_resp("/files/readme.pdf")
|
189
|
+
assert_equal "Other file: readme.pdf", res_other.body
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Test a deeper nested structure that mimics a versioned API with sub-resources.
|
194
|
+
def test_complex_nested_structure
|
195
|
+
server(
|
196
|
+
itsi_rb: lambda do
|
197
|
+
location "/api" do
|
198
|
+
location "/v1" do
|
199
|
+
location "/users" do
|
200
|
+
location "/:user_id" do
|
201
|
+
location "/profile" do
|
202
|
+
get("/") { |r, params| r.respond("User Profile for #{r.params[:user_id]}") }
|
203
|
+
end
|
204
|
+
location "/orders" do
|
205
|
+
get("/") { |r, params| r.respond("User Orders for #{r.params[:user_id]}") }
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
) do
|
213
|
+
res_profile = get_resp("/api/v1/users/42/profile")
|
214
|
+
assert_equal "User Profile for 42", res_profile.body
|
215
|
+
|
216
|
+
res_orders = get_resp("/api/v1/users/42/orders")
|
217
|
+
assert_equal "User Orders for 42", res_orders.body
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "../helpers/test_helper"
|
2
|
+
|
3
|
+
class TestMaxBody < Minitest::Test
|
4
|
+
def test_max_body_enforced
|
5
|
+
server(
|
6
|
+
itsi_rb: lambda do
|
7
|
+
max_body limit_bytes: 20
|
8
|
+
post("/") { |r|
|
9
|
+
r.ok "OK"
|
10
|
+
}
|
11
|
+
end
|
12
|
+
) do
|
13
|
+
small = "a" * 10
|
14
|
+
large = "b" * 100
|
15
|
+
|
16
|
+
assert_equal "200", post("/", small).code
|
17
|
+
assert_equal "413", post("/", large).code
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|