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,415 @@
|
|
1
|
+
require_relative "../helpers/test_helper"
|
2
|
+
|
3
|
+
class TestProxy < Minitest::Test
|
4
|
+
|
5
|
+
def test_successful_forwarding
|
6
|
+
|
7
|
+
backend_bind = free_bind
|
8
|
+
server(
|
9
|
+
itsi_rb: lambda do
|
10
|
+
log_requests before: { format: "GET {path_and_query}", level: "INFO"}
|
11
|
+
get("/foo") { |r|
|
12
|
+
r.ok "backend success. #{r.query_params["bar"]}"
|
13
|
+
}
|
14
|
+
end,
|
15
|
+
bind: backend_bind
|
16
|
+
) do
|
17
|
+
server(
|
18
|
+
itsi_rb: lambda do
|
19
|
+
proxy \
|
20
|
+
to: "#{backend_bind}{path_and_query}",
|
21
|
+
backends: ["#{backend_bind}"],
|
22
|
+
backend_priority: "round_robin",
|
23
|
+
headers: {},
|
24
|
+
verify_ssl: false,
|
25
|
+
timeout: 30,
|
26
|
+
tls_sni: false,
|
27
|
+
error_response: "internal_server_error"
|
28
|
+
get("/foo") { |r|
|
29
|
+
r.ok "should not get here"
|
30
|
+
}
|
31
|
+
end
|
32
|
+
) do
|
33
|
+
res = get_resp("/foo?bar=baz")
|
34
|
+
# Expect that the proxy forwards the request to the backend.
|
35
|
+
assert_equal "200", res.code, "Expected success status from backend"
|
36
|
+
assert_equal "backend success. baz", res.body
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Test that an invalid target URL (i.e. an unparseable URL) yields an error response.
|
42
|
+
def test_invalid_target_url_returns_error
|
43
|
+
server(
|
44
|
+
itsi_rb: lambda do
|
45
|
+
proxy \
|
46
|
+
to: "not_a_valid_url",
|
47
|
+
backends: ["127.0.0.1:3001"],
|
48
|
+
backend_priority: "round_robin",
|
49
|
+
headers: {},
|
50
|
+
verify_ssl: false,
|
51
|
+
timeout: 30,
|
52
|
+
tls_sni: false,
|
53
|
+
error_response: "bad_gateway"
|
54
|
+
get("/foo") { |r| r.ok "should not get here" }
|
55
|
+
end
|
56
|
+
) do
|
57
|
+
res = get_resp("/foo")
|
58
|
+
assert_equal "502", res.code, "Expected error response code 502 for invalid URL"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_overriding_headers
|
63
|
+
|
64
|
+
backend_bind = free_bind
|
65
|
+
server(
|
66
|
+
itsi_rb: lambda do
|
67
|
+
log_requests before: { format: "GET {path_and_query}", level: "INFO"}
|
68
|
+
get("/header-test") do |r|
|
69
|
+
# Return the incoming header value.
|
70
|
+
r.ok r.header("X-Forwarded-For").first
|
71
|
+
end
|
72
|
+
end,
|
73
|
+
bind: backend_bind
|
74
|
+
) do
|
75
|
+
|
76
|
+
# Start proxy server.
|
77
|
+
server(
|
78
|
+
itsi_rb: lambda do
|
79
|
+
proxy \
|
80
|
+
to: "#{backend_bind}{path}{query}",
|
81
|
+
backends: ["#{backend_bind}"],
|
82
|
+
backend_priority: "round_robin",
|
83
|
+
headers: { "X-Forwarded-For" => "{addr}" },
|
84
|
+
verify_ssl: false,
|
85
|
+
timeout: 30,
|
86
|
+
tls_sni: false,
|
87
|
+
error_response: "internal_server_error"
|
88
|
+
get("/header-test") { |r| r.ok "should not get here" }
|
89
|
+
end
|
90
|
+
) do
|
91
|
+
res = get_resp("/header-test")
|
92
|
+
# Expect that the overriding header "X-Forwarded-For" is set to the client’s address.
|
93
|
+
assert_equal "127.0.0.1", res.body, "Expected the header to be set to the client address"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_proxy_with_static_to_only
|
99
|
+
backend_bind = free_bind
|
100
|
+
server(
|
101
|
+
itsi_rb: lambda do
|
102
|
+
get("/static") { |r| r.ok "static response" }
|
103
|
+
end,
|
104
|
+
bind: backend_bind
|
105
|
+
) do
|
106
|
+
server(
|
107
|
+
itsi_rb: lambda do
|
108
|
+
proxy \
|
109
|
+
to: "#{backend_bind}{path}{query}",
|
110
|
+
backend_priority: "round_robin",
|
111
|
+
headers: {},
|
112
|
+
verify_ssl: false,
|
113
|
+
timeout: 30,
|
114
|
+
tls_sni: false,
|
115
|
+
error_response: "internal_server_error"
|
116
|
+
get("/static") { |r| r.ok "should not get here" }
|
117
|
+
end
|
118
|
+
) do
|
119
|
+
res = get_resp("/static")
|
120
|
+
assert_equal "200", res.code, "Expected success status from static 'to' URL"
|
121
|
+
assert_equal "static response", res.body
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_proxy_with_backend_host_override
|
127
|
+
backend_bind1 = free_bind
|
128
|
+
backend_bind2 = free_bind
|
129
|
+
# Start two dummy backends that respond with their Host header.
|
130
|
+
thread1 = Thread.new do
|
131
|
+
server(
|
132
|
+
itsi_rb: lambda do
|
133
|
+
get("/host-test") { |r| r.ok r.header("Host").first }
|
134
|
+
end,
|
135
|
+
bind: backend_bind1
|
136
|
+
){ sleep 1 }
|
137
|
+
end
|
138
|
+
thread2 = Thread.new do
|
139
|
+
server(
|
140
|
+
itsi_rb: lambda do
|
141
|
+
get("/host-test") { |r| r.ok r.header("Host").first }
|
142
|
+
end,
|
143
|
+
bind: backend_bind2
|
144
|
+
){ sleep 1 }
|
145
|
+
end
|
146
|
+
|
147
|
+
sleep 0.1
|
148
|
+
|
149
|
+
server(
|
150
|
+
itsi_rb: lambda do
|
151
|
+
proxy \
|
152
|
+
to: "#{backend_bind1}{path}{query}",
|
153
|
+
backends: ["#{backend_bind1}", "#{backend_bind2}"],
|
154
|
+
backend_priority: "round_robin",
|
155
|
+
headers: { "Host" => "custom.backend.example.com" },
|
156
|
+
verify_ssl: false,
|
157
|
+
timeout: 30,
|
158
|
+
tls_sni: true,
|
159
|
+
error_response: "internal_server_error"
|
160
|
+
get("/host-test") { |r| r.ok "should not get here" }
|
161
|
+
end
|
162
|
+
) do
|
163
|
+
res = get_resp("/host-test")
|
164
|
+
assert_equal "200", res.code, "Expected successful response with host override"
|
165
|
+
assert_equal "custom.backend.example.com", res.body, "Expected the Host header to be overridden"
|
166
|
+
end
|
167
|
+
|
168
|
+
thread1.kill
|
169
|
+
thread2.kill
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_proxy_timeout
|
173
|
+
backend_bind = free_bind
|
174
|
+
# Start a backend server that sleeps for 1 second before responding.
|
175
|
+
server(
|
176
|
+
itsi_rb: lambda do
|
177
|
+
get("/slow") do |r|
|
178
|
+
sleep 1
|
179
|
+
r.ok "delayed response"
|
180
|
+
end
|
181
|
+
end,
|
182
|
+
bind: backend_bind
|
183
|
+
) do
|
184
|
+
server(
|
185
|
+
itsi_rb: lambda do
|
186
|
+
# Set a very short timeout (0 seconds) to force a timeout.
|
187
|
+
proxy \
|
188
|
+
to: "#{backend_bind}{path}{query}",
|
189
|
+
backends: ["#{backend_bind}"],
|
190
|
+
backend_priority: "round_robin",
|
191
|
+
headers: {},
|
192
|
+
verify_ssl: false,
|
193
|
+
timeout: 0,
|
194
|
+
tls_sni: false,
|
195
|
+
error_response: "gateway_timeout"
|
196
|
+
get("/slow") { |r| r.ok "should not get here" }
|
197
|
+
end
|
198
|
+
) do
|
199
|
+
res = get_resp("/slow")
|
200
|
+
assert_equal "504", res.code, "Expected 504 Gateway Timeout when backend response exceeds timeout"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_error_response_internal_server_error
|
206
|
+
server(
|
207
|
+
itsi_rb: lambda do
|
208
|
+
proxy \
|
209
|
+
to: "invalid_url",
|
210
|
+
backends: ["127.0.0.1:3001"],
|
211
|
+
backend_priority: "round_robin",
|
212
|
+
headers: {},
|
213
|
+
verify_ssl: false,
|
214
|
+
timeout: 30,
|
215
|
+
tls_sni: false,
|
216
|
+
error_response: "internal_server_error"
|
217
|
+
get("/foo") { |r| r.ok "should not get here" }
|
218
|
+
end
|
219
|
+
) do
|
220
|
+
res = get_resp("/foo")
|
221
|
+
assert_equal "500", res.code, "Expected error response code 500 for internal_server_error"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_error_response_not_found
|
226
|
+
server(
|
227
|
+
itsi_rb: lambda do
|
228
|
+
proxy \
|
229
|
+
to: "invalid_url",
|
230
|
+
backends: ["127.0.0.1:3001"],
|
231
|
+
backend_priority: "round_robin",
|
232
|
+
headers: {},
|
233
|
+
verify_ssl: false,
|
234
|
+
timeout: 30,
|
235
|
+
tls_sni: false,
|
236
|
+
error_response: "not_found"
|
237
|
+
get("/foo") { |r| r.ok "should not get here" }
|
238
|
+
end
|
239
|
+
) do
|
240
|
+
res = get_resp("/foo")
|
241
|
+
assert_equal "404", res.code, "Expected error response code 404 for not_found"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_error_response_unauthorized
|
246
|
+
server(
|
247
|
+
itsi_rb: lambda do
|
248
|
+
proxy \
|
249
|
+
to: "invalid_url",
|
250
|
+
backends: ["127.0.0.1:3001"],
|
251
|
+
backend_priority: "round_robin",
|
252
|
+
headers: {},
|
253
|
+
verify_ssl: false,
|
254
|
+
timeout: 30,
|
255
|
+
tls_sni: false,
|
256
|
+
error_response: "unauthorized"
|
257
|
+
get("/foo") { |r| r.ok "should not get here" }
|
258
|
+
end
|
259
|
+
) do
|
260
|
+
res = get_resp("/foo")
|
261
|
+
assert_equal "401", res.code, "Expected error response code 401 for unauthorized"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def test_error_response_forbidden
|
266
|
+
server(
|
267
|
+
itsi_rb: lambda do
|
268
|
+
proxy \
|
269
|
+
to: "invalid_url",
|
270
|
+
backends: ["127.0.0.1:3001"],
|
271
|
+
backend_priority: "round_robin",
|
272
|
+
headers: {},
|
273
|
+
verify_ssl: false,
|
274
|
+
timeout: 30,
|
275
|
+
tls_sni: false,
|
276
|
+
error_response: "forbidden"
|
277
|
+
get("/foo") { |r| r.ok "should not get here" }
|
278
|
+
end
|
279
|
+
) do
|
280
|
+
res = get_resp("/foo")
|
281
|
+
assert_equal "403", res.code, "Expected error response code 403 for forbidden"
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_error_response_payload_too_large
|
286
|
+
server(
|
287
|
+
itsi_rb: lambda do
|
288
|
+
proxy \
|
289
|
+
to: "invalid_url",
|
290
|
+
backends: ["127.0.0.1:3001"],
|
291
|
+
backend_priority: "round_robin",
|
292
|
+
headers: {},
|
293
|
+
verify_ssl: false,
|
294
|
+
timeout: 30,
|
295
|
+
tls_sni: false,
|
296
|
+
error_response: "payload_too_large"
|
297
|
+
get("/foo") { |r| r.ok "should not get here" }
|
298
|
+
end
|
299
|
+
) do
|
300
|
+
res = get_resp("/foo")
|
301
|
+
assert_equal "413", res.code, "Expected error response code 413 for payload_too_large"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def test_error_response_too_many_requests
|
306
|
+
server(
|
307
|
+
itsi_rb: lambda do
|
308
|
+
proxy \
|
309
|
+
to: "invalid_url",
|
310
|
+
backends: ["127.0.0.1:3001"],
|
311
|
+
backend_priority: "round_robin",
|
312
|
+
headers: {},
|
313
|
+
verify_ssl: false,
|
314
|
+
timeout: 30,
|
315
|
+
tls_sni: false,
|
316
|
+
error_response: "too_many_requests"
|
317
|
+
get("/foo") { |r| r.ok "should not get here" }
|
318
|
+
end
|
319
|
+
) do
|
320
|
+
res = get_resp("/foo")
|
321
|
+
assert_equal "429", res.code, "Expected error response code 429 for too_many_requests"
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def test_error_response_service_unavailable
|
326
|
+
server(
|
327
|
+
itsi_rb: lambda do
|
328
|
+
proxy \
|
329
|
+
to: "invalid_url",
|
330
|
+
backends: ["127.0.0.1:3001"],
|
331
|
+
backend_priority: "round_robin",
|
332
|
+
headers: {},
|
333
|
+
verify_ssl: false,
|
334
|
+
timeout: 30,
|
335
|
+
tls_sni: false,
|
336
|
+
error_response: "service_unavailable"
|
337
|
+
get("/foo") { |r| r.ok "should not get here" }
|
338
|
+
end
|
339
|
+
) do
|
340
|
+
res = get_resp("/foo")
|
341
|
+
assert_equal "503", res.code, "Expected error response code 503 for service_unavailable"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def test_error_response_gateway_timeout
|
346
|
+
server(
|
347
|
+
itsi_rb: lambda do
|
348
|
+
proxy \
|
349
|
+
to: "invalid_url",
|
350
|
+
backends: ["127.0.0.1:3001"],
|
351
|
+
backend_priority: "round_robin",
|
352
|
+
headers: {},
|
353
|
+
verify_ssl: false,
|
354
|
+
timeout: 30,
|
355
|
+
tls_sni: false,
|
356
|
+
error_response: "gateway_timeout"
|
357
|
+
get("/foo") { |r| r.ok "should not get here" }
|
358
|
+
end
|
359
|
+
) do
|
360
|
+
res = get_resp("/foo")
|
361
|
+
assert_equal "504", res.code, "Expected error response code 504 for gateway_timeout"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def test_failover_behavior
|
366
|
+
# Obtain two free bind addresses for backend servers.
|
367
|
+
backend1_bind = free_bind
|
368
|
+
backend2_bind = free_bind
|
369
|
+
|
370
|
+
backend2_server = Itsi::Server.start_in_background_thread(binds: [backend2_bind]) do
|
371
|
+
get("/failover") { |r| r.ok "backend2" }
|
372
|
+
end
|
373
|
+
|
374
|
+
# backend2_server.stop
|
375
|
+
|
376
|
+
# # Allow the backend servers to start.
|
377
|
+
sleep 0.2
|
378
|
+
|
379
|
+
|
380
|
+
# Start the proxy server with an ordered backend selection.
|
381
|
+
server(
|
382
|
+
cleanup: false,
|
383
|
+
itsi_rb: lambda do
|
384
|
+
proxy \
|
385
|
+
to: "http://proxied_host.com{path}{query}",
|
386
|
+
backends: [backend1_bind[/\/\/(.*)/,1], backend2_bind[/\/\/(.*)/,1]],
|
387
|
+
backend_priority: "ordered",
|
388
|
+
headers: {},
|
389
|
+
verify_ssl: false,
|
390
|
+
timeout: 30,
|
391
|
+
tls_sni: false,
|
392
|
+
error_response: "internal_server_error"
|
393
|
+
get("/failover") { |r| r.ok "should not get here" }
|
394
|
+
end
|
395
|
+
) do
|
396
|
+
|
397
|
+
|
398
|
+
res = get_resp("/failover")
|
399
|
+
assert_equal "200", res.code, "Expected success when fallback backend is available"
|
400
|
+
assert_equal "backend2", res.body, "Expected response from backend2"
|
401
|
+
|
402
|
+
backend1_server = Itsi::Server.start_in_background_thread(binds: [backend1_bind]) do
|
403
|
+
get("/failover") { |r| r.ok "backend1" }
|
404
|
+
end
|
405
|
+
|
406
|
+
sleep 1
|
407
|
+
|
408
|
+
res = get_resp("/failover")
|
409
|
+
assert_equal "200", res.code, "Expected reversion when primary becomes available"
|
410
|
+
assert_equal "backend1", res.body, "Expected response from backend1 with ordered priority"
|
411
|
+
end
|
412
|
+
|
413
|
+
Itsi::Server.stop_background_threads
|
414
|
+
end
|
415
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require_relative "../helpers/test_helper"
|
2
|
+
require "redis"
|
3
|
+
|
4
|
+
class TestRateLimit < Minitest::Test
|
5
|
+
|
6
|
+
# 1. In‑memory: allow up to N requests, then 429
|
7
|
+
def test_in_memory_limit
|
8
|
+
server(
|
9
|
+
itsi_rb: lambda do
|
10
|
+
rate_limit requests: 3, seconds: 2
|
11
|
+
get("/foo") { |r| r.ok "ok" }
|
12
|
+
end
|
13
|
+
) do
|
14
|
+
3.times do
|
15
|
+
res = get_resp("/foo")
|
16
|
+
assert_equal "200", res.code
|
17
|
+
assert_equal "ok", res.body
|
18
|
+
end
|
19
|
+
# Next one should be rate‑limited
|
20
|
+
res = get_resp("/foo")
|
21
|
+
assert_equal "429", res.code
|
22
|
+
# default error_response body is the standard message
|
23
|
+
assert_match /Slow down!/, res.body
|
24
|
+
assert_match /429/, res.body
|
25
|
+
|
26
|
+
assert_equal 3.to_s, res["X-RateLimit-Limit"]
|
27
|
+
assert_equal "0", res["X-RateLimit-Remaining"]
|
28
|
+
assert_match /\d+/, res["X-RateLimit-Reset"]
|
29
|
+
assert_equal res["X-RateLimit-Reset"], res["Retry-After"]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# 2. Time window resets after `seconds`
|
34
|
+
def test_window_reset
|
35
|
+
server(
|
36
|
+
itsi_rb: lambda do
|
37
|
+
rate_limit requests: 1, seconds: 1
|
38
|
+
get("/bar") { |r| r.ok "bar" }
|
39
|
+
end
|
40
|
+
) do
|
41
|
+
res1 = get_resp("/bar")
|
42
|
+
assert_equal "200", res1.code
|
43
|
+
|
44
|
+
res2 = get_resp("/bar")
|
45
|
+
assert_equal "429", res2.code
|
46
|
+
|
47
|
+
sleep 1.1
|
48
|
+
res3 = get_resp("/bar")
|
49
|
+
assert_equal "200", res3.code
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# 3. Key by header
|
54
|
+
def test_key_by_header
|
55
|
+
server(
|
56
|
+
itsi_rb: lambda do
|
57
|
+
rate_limit \
|
58
|
+
requests: 1,
|
59
|
+
seconds: 60,
|
60
|
+
key: { parameter: { header: { name: "X-Client-Id" } } }
|
61
|
+
get("/h") { |r| r.ok r.header("X-Client-Id").first }
|
62
|
+
end
|
63
|
+
) do
|
64
|
+
h1 = { "X-Client-Id" => "A" }
|
65
|
+
h2 = { "X-Client-Id" => "B" }
|
66
|
+
|
67
|
+
# A once OK, then limited
|
68
|
+
res1 = get_resp("/h", h1)
|
69
|
+
assert_equal "200", res1.code
|
70
|
+
assert_equal "A", res1.body
|
71
|
+
|
72
|
+
res2 = get_resp("/h", h1)
|
73
|
+
assert_equal "429", res2.code
|
74
|
+
|
75
|
+
# B independent count
|
76
|
+
res3 = get_resp("/h", h2)
|
77
|
+
assert_equal "200", res3.code
|
78
|
+
assert_equal "B", res3.body
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# 4. Key by query
|
83
|
+
def test_key_by_query
|
84
|
+
server(
|
85
|
+
itsi_rb: lambda do
|
86
|
+
rate_limit \
|
87
|
+
requests: 1,
|
88
|
+
seconds: 60,
|
89
|
+
key: { parameter: { query: "user" } }
|
90
|
+
get("/q") { |r| r.ok r.query_params["user"] }
|
91
|
+
end
|
92
|
+
) do
|
93
|
+
res1 = get_resp("/q?user=foo")
|
94
|
+
assert_equal "200", res1.code
|
95
|
+
assert_equal "foo", res1.body
|
96
|
+
|
97
|
+
res2 = get_resp("/q?user=foo")
|
98
|
+
assert_equal "429", res2.code
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# 5. Custom error_response
|
103
|
+
def test_custom_error_response
|
104
|
+
server(
|
105
|
+
itsi_rb: lambda do
|
106
|
+
rate_limit \
|
107
|
+
requests: 1,
|
108
|
+
seconds: 60,
|
109
|
+
error_response: {
|
110
|
+
code: 429,
|
111
|
+
plaintext: { inline: "Slow down" },
|
112
|
+
default: "plaintext"
|
113
|
+
}
|
114
|
+
get("/") { |r| r.ok "never" }
|
115
|
+
end
|
116
|
+
) do
|
117
|
+
res = 5.times.map{ get_resp("/") }.last
|
118
|
+
assert_equal "429", res.code
|
119
|
+
assert_equal "Slow down", res.body
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# 6. Skip Redis tests if Redis not available
|
124
|
+
def test_redis_store_unavailable_skips
|
125
|
+
skip "Redis not running" unless begin
|
126
|
+
Redis.new(url: ENV.fetch("REDIS_URL","redis://localhost:6379/15")).ping == "PONG"
|
127
|
+
rescue
|
128
|
+
false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# 7. Redis‑backed limiting
|
133
|
+
def test_redis_backed_limit
|
134
|
+
redis_url = ENV.fetch("REDIS_URL","redis://localhost:6379/15")
|
135
|
+
ENV["REDIS_URL"] = redis_url
|
136
|
+
server(
|
137
|
+
itsi_rb: lambda do
|
138
|
+
rate_limit \
|
139
|
+
requests: 2,
|
140
|
+
seconds: 60,
|
141
|
+
store_config: { redis: { connection_url: ENV["REDIS_URL"] } }
|
142
|
+
get("/r") { |r| r.ok "ok" }
|
143
|
+
end
|
144
|
+
) do
|
145
|
+
client = Redis.new(url: redis_url)
|
146
|
+
client.flushdb
|
147
|
+
|
148
|
+
2.times do
|
149
|
+
res = get_resp("/r")
|
150
|
+
assert_equal "200", res.code
|
151
|
+
assert_equal "ok", res.body
|
152
|
+
end
|
153
|
+
|
154
|
+
# Third hit is rate‑limited
|
155
|
+
res3 = get_resp("/r")
|
156
|
+
assert_equal "429", res3.code
|
157
|
+
|
158
|
+
client.flushdb
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# 8. Trusted proxy: forwarded IP is used for rate limit key
|
163
|
+
def test_trusted_proxy_respects_forwarded_ip
|
164
|
+
server(
|
165
|
+
itsi_rb: lambda do
|
166
|
+
rate_limit \
|
167
|
+
requests: 1,
|
168
|
+
seconds: 60,
|
169
|
+
trusted_proxies: {
|
170
|
+
"127.0.0.1" => { header: { name: "X-Forwarded-For" } }
|
171
|
+
}
|
172
|
+
get("/ip") { |r| r.ok "ok" }
|
173
|
+
end
|
174
|
+
) do
|
175
|
+
# First IP: allowed
|
176
|
+
res1 = get_resp("/ip", { "X-Forwarded-For" => "198.51.100.1" })
|
177
|
+
assert_equal "200", res1.code
|
178
|
+
|
179
|
+
# Second hit from same client IP: blocked
|
180
|
+
res2 = get_resp("/ip", { "X-Forwarded-For" => "198.51.100.1" })
|
181
|
+
assert_equal "429", res2.code
|
182
|
+
|
183
|
+
# Third hit from *different* IP: allowed
|
184
|
+
res3 = get_resp("/ip", { "X-Forwarded-For" => "198.51.100.2" })
|
185
|
+
assert_equal "200", res3.code
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# 9. Untrusted proxy: forwarded header is ignored
|
190
|
+
def test_untrusted_proxy_ignores_forwarded_ip
|
191
|
+
server(
|
192
|
+
itsi_rb: lambda do
|
193
|
+
rate_limit \
|
194
|
+
requests: 1,
|
195
|
+
seconds: 60,
|
196
|
+
trusted_proxies: {
|
197
|
+
"10.0.0.1" => { header: { name: "X-Forwarded-For" } }
|
198
|
+
}
|
199
|
+
get("/untrusted") { |r| r.ok "ok" }
|
200
|
+
end
|
201
|
+
) do
|
202
|
+
# Even though header says different IP, it’s ignored (127.0.0.1 is used)
|
203
|
+
res1 = get_resp("/untrusted", { "X-Forwarded-For" => "198.51.100.1" })
|
204
|
+
assert_equal "200", res1.code
|
205
|
+
|
206
|
+
# Still treated as same client (127.0.0.1), so next request is blocked
|
207
|
+
res2 = get_resp("/untrusted", { "X-Forwarded-For" => "198.51.100.2" })
|
208
|
+
assert_equal "429", res2.code
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require_relative "../helpers/test_helper"
|
2
|
+
|
3
|
+
class TestRedirect < Minitest::Test
|
4
|
+
def test_permanent_redirect
|
5
|
+
server(
|
6
|
+
itsi_rb: lambda do
|
7
|
+
redirect to: "https://example.com/new", type: "moved_permanently"
|
8
|
+
get("/foo") { |r| r.ok "should not get here" }
|
9
|
+
end
|
10
|
+
) do
|
11
|
+
res = get_resp("/foo")
|
12
|
+
assert_equal "301", res.code, "Expected status 301 for permanent redirect"
|
13
|
+
assert_equal "https://example.com/new", res["Location"]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_temporary_redirect
|
18
|
+
server(
|
19
|
+
itsi_rb: lambda do
|
20
|
+
redirect to: "https://example.com/new", type: "temporary"
|
21
|
+
get("/foo") { |r| r.ok "should not get here" }
|
22
|
+
end
|
23
|
+
) do
|
24
|
+
res = get_resp("/foo")
|
25
|
+
assert_equal "307", res.code, "Expected status 307 for temporary redirect"
|
26
|
+
assert_equal "https://example.com/new", res["Location"]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_found_redirect
|
31
|
+
server(
|
32
|
+
itsi_rb: lambda do
|
33
|
+
redirect to: "https://example.com/new", type: "found"
|
34
|
+
get("/foo") { |r| r.ok "should not get here" }
|
35
|
+
end
|
36
|
+
) do
|
37
|
+
res = get_resp("/foo")
|
38
|
+
assert_equal "302", res.code, "Expected status 302 for found redirect"
|
39
|
+
assert_equal "https://example.com/new", res["Location"]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_moved_permanently_redirect
|
44
|
+
server(
|
45
|
+
itsi_rb: lambda do
|
46
|
+
redirect to: "https://example.com/new", type: "permanent"
|
47
|
+
get("/foo") { |r| r.ok "should not get here" }
|
48
|
+
end
|
49
|
+
) do
|
50
|
+
res = get_resp("/foo")
|
51
|
+
assert_equal "308", res.code, "Expected status 308 for moved permanently redirect"
|
52
|
+
assert_equal "https://example.com/new", res["Location"]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_relative_redirect
|
57
|
+
server(
|
58
|
+
itsi_rb: lambda do
|
59
|
+
# Use a relative URL as the target
|
60
|
+
redirect to: "/new/path", type: "permanent"
|
61
|
+
get("/foo") { |r| r.ok "should not get here" }
|
62
|
+
end
|
63
|
+
) do
|
64
|
+
res = get_resp("/foo")
|
65
|
+
assert_equal "308", res.code, "Expected status 308 for permanent redirect"
|
66
|
+
# For a relative URL, the Location header should match exactly
|
67
|
+
assert_equal "/new/path", res["Location"]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_relative_redirect_with_placeholder
|
72
|
+
server(
|
73
|
+
itsi_rb: lambda do
|
74
|
+
# Use a template that interpolates the request path into a relative URL.
|
75
|
+
# For a request to "/foo", the resulting Location should be "/new/foo".
|
76
|
+
redirect to: "/new{path}", type: "temporary"
|
77
|
+
get("/foo") { |r| r.ok "should not get here" }
|
78
|
+
end
|
79
|
+
) do
|
80
|
+
res = get_resp("/foo")
|
81
|
+
assert_equal "307", res.code, "Expected status 307 for temporary redirect"
|
82
|
+
assert_equal "/new/foo", res["Location"]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|