itsi 0.1.9 → 0.1.12
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/Cargo.lock +1542 -43
- data/Itsi.rb +125 -0
- data/Rakefile +8 -4
- data/crates/itsi_error/src/lib.rs +9 -0
- data/crates/itsi_rb_helpers/Cargo.toml +1 -0
- data/crates/itsi_rb_helpers/src/heap_value.rs +18 -0
- data/crates/itsi_rb_helpers/src/lib.rs +59 -9
- data/crates/itsi_server/Cargo.toml +70 -28
- data/crates/itsi_server/src/lib.rs +80 -80
- data/crates/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
- data/{gems/server/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs → crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs} +22 -3
- data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
- data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
- data/crates/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
- data/crates/itsi_server/src/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +72 -41
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +82 -0
- data/crates/itsi_server/src/ruby_types/mod.rs +55 -0
- data/crates/itsi_server/src/server/bind.rs +29 -17
- data/crates/itsi_server/src/server/byte_frame.rs +32 -0
- data/crates/itsi_server/src/server/cache_store.rs +74 -0
- data/crates/itsi_server/src/server/itsi_service.rs +172 -0
- data/crates/itsi_server/src/server/lifecycle_event.rs +3 -0
- data/crates/itsi_server/src/server/listener.rs +111 -11
- data/crates/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +315 -0
- data/crates/itsi_server/src/server/mod.rs +8 -1
- data/crates/itsi_server/src/server/process_worker.rs +44 -11
- data/crates/itsi_server/src/server/rate_limiter.rs +565 -0
- data/crates/itsi_server/src/server/request_job.rs +11 -0
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +129 -46
- data/crates/itsi_server/src/server/serve_strategy/mod.rs +9 -6
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +325 -167
- data/crates/itsi_server/src/server/signal.rs +20 -4
- data/crates/itsi_server/src/server/static_file_server.rs +984 -0
- data/crates/itsi_server/src/server/thread_worker.rs +165 -88
- data/crates/itsi_server/src/server/tls.rs +1 -1
- data/crates/itsi_server/src/server/types.rs +43 -0
- data/crates/itsi_server/test.md +14 -0
- data/crates/itsi_tracing/Cargo.toml +1 -0
- data/crates/itsi_tracing/src/lib.rs +216 -45
- data/docs/.gitignore +7 -0
- data/docs/.gitpod.yml +15 -0
- data/docs/Itsi.rb +17 -0
- data/docs/content/_index.md +17 -0
- data/docs/content/about.md +6 -0
- data/docs/content/docs/_index.md +18 -0
- data/docs/content/docs/first-page.md +9 -0
- data/docs/content/docs/folder/_index.md +10 -0
- data/docs/content/docs/folder/leaf.md +7 -0
- data/docs/go.mod +5 -0
- data/docs/go.sum +2 -0
- data/docs/hugo.yaml +77 -0
- data/examples/static_assets_example.rb +83 -0
- data/gems/_index.md +18 -0
- data/gems/scheduler/CODE_OF_CONDUCT.md +7 -0
- data/gems/scheduler/Cargo.lock +75 -14
- data/gems/scheduler/README.md +5 -0
- data/gems/scheduler/_index.md +7 -0
- data/gems/scheduler/itsi-scheduler.gemspec +4 -1
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/lib/itsi/scheduler.rb +2 -2
- data/gems/scheduler/test/test_file_io.rb +0 -1
- data/gems/scheduler/test/test_itsi_scheduler.rb +1 -1
- data/gems/server/CHANGELOG.md +5 -0
- data/gems/server/CODE_OF_CONDUCT.md +7 -0
- data/gems/server/Cargo.lock +1543 -43
- data/gems/server/README.md +4 -0
- data/gems/server/_index.md +6 -0
- data/gems/server/exe/itsi +46 -57
- data/gems/server/itsi-server.gemspec +3 -2
- data/gems/server/lib/itsi/{request.rb → http_request.rb} +29 -5
- data/gems/server/lib/itsi/http_response.rb +39 -0
- data/gems/server/lib/itsi/server/Itsi.rb +119 -0
- data/gems/server/lib/itsi/server/config/dsl.rb +506 -0
- data/gems/server/lib/itsi/server/config.rb +131 -0
- data/gems/server/lib/itsi/server/default_app/default_app.rb +38 -0
- data/gems/server/lib/itsi/server/grpc_interface.rb +213 -0
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +9 -6
- data/gems/server/lib/itsi/server/rack_interface.rb +24 -9
- data/gems/server/lib/itsi/server/scheduler_interface.rb +1 -1
- data/gems/server/lib/itsi/server/scheduler_mode.rb +4 -0
- data/gems/server/lib/itsi/server/signal_trap.rb +6 -1
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +75 -60
- data/gems/server/lib/itsi/standard_headers.rb +86 -0
- data/gems/server/test/helpers/test_helper.rb +14 -12
- data/gems/server/test/test_itsi_server.rb +21 -2
- data/lib/itsi/version.rb +1 -1
- data/sandbox/itsi_file/Gemfile +11 -0
- data/sandbox/itsi_file/Gemfile.lock +69 -0
- data/sandbox/itsi_file/Itsi.rb +276 -0
- data/sandbox/itsi_file/error.html +2 -0
- data/sandbox/itsi_file/organisations_controller.rb +20 -0
- data/sandbox/itsi_file/public/assets/image.png +0 -0
- data/sandbox/itsi_file/public/assets/index.html +1 -0
- data/sandbox/itsi_sandbox_async/Gemfile +1 -1
- data/sandbox/itsi_sandbox_hanami/Gemfile.lock +2 -2
- data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
- data/sandbox/itsi_sandbox_rack/config.ru +2 -15
- data/sandbox/itsi_sandbox_rails/.dockerignore +2 -5
- data/sandbox/itsi_sandbox_rails/.github/workflows/ci.yml +1 -1
- data/sandbox/itsi_sandbox_rails/.gitignore +2 -1
- data/sandbox/itsi_sandbox_rails/Dockerfile +6 -9
- data/sandbox/itsi_sandbox_rails/Gemfile +16 -22
- data/sandbox/itsi_sandbox_rails/Gemfile.lock +100 -225
- data/sandbox/itsi_sandbox_rails/app/assets/config/manifest.js +4 -0
- data/sandbox/itsi_sandbox_rails/app/assets/stylesheets/application.css +11 -6
- data/sandbox/itsi_sandbox_rails/app/channels/application_cable/channel.rb +4 -0
- data/sandbox/itsi_sandbox_rails/app/channels/application_cable/connection.rb +4 -0
- data/sandbox/itsi_sandbox_rails/app/controllers/live_controller.rb +7 -8
- data/sandbox/itsi_sandbox_rails/app/controllers/uploads_controller.rb +0 -3
- data/sandbox/itsi_sandbox_rails/app/views/layouts/application.html.erb +2 -7
- data/sandbox/itsi_sandbox_rails/bin/docker-entrypoint +3 -4
- data/sandbox/itsi_sandbox_rails/bin/setup +8 -5
- data/sandbox/itsi_sandbox_rails/config/application.rb +1 -35
- data/sandbox/itsi_sandbox_rails/config/cable.yml +3 -10
- data/sandbox/itsi_sandbox_rails/config/credentials.yml.enc +1 -1
- data/sandbox/itsi_sandbox_rails/config/database.yml +9 -19
- data/sandbox/itsi_sandbox_rails/config/environment.rb +1 -1
- data/sandbox/itsi_sandbox_rails/config/environments/development.rb +21 -12
- data/sandbox/itsi_sandbox_rails/config/environments/production.rb +49 -34
- data/sandbox/itsi_sandbox_rails/config/environments/test.rb +19 -5
- data/sandbox/itsi_sandbox_rails/config/initializers/assets.rb +5 -0
- data/sandbox/itsi_sandbox_rails/config/initializers/filter_parameter_logging.rb +1 -1
- data/sandbox/itsi_sandbox_rails/config/initializers/permissions_policy.rb +13 -0
- data/sandbox/itsi_sandbox_rails/config/puma.rb +2 -9
- data/sandbox/itsi_sandbox_rails/config.ru +0 -1
- data/sandbox/itsi_sandbox_rails/db/migrate/20250301041554_create_posts.rb +1 -1
- data/sandbox/itsi_sandbox_rails/db/schema.rb +2 -2
- data/sandbox/itsi_sandbox_rails/lib/assets/.keep +0 -0
- data/sandbox/itsi_sandbox_rails/public/404.html +66 -113
- data/sandbox/itsi_sandbox_rails/public/406-unsupported-browser.html +65 -113
- data/sandbox/itsi_sandbox_rails/public/422.html +66 -113
- data/sandbox/itsi_sandbox_rails/public/500.html +65 -113
- data/sandbox/itsi_sandbox_rails/public/icon.png +0 -0
- data/sandbox/itsi_sandbox_rails/public/icon.svg +2 -2
- data/sandbox/itsi_sandbox_rails/test/channels/application_cable/connection_test.rb +13 -0
- data/sandbox/itsi_sandbox_roda/Gemfile.lock +3 -10
- data/tasks.txt +72 -12
- metadata +94 -139
- data/crates/itsi_server/src/body_proxy/itsi_body_proxy.rs +0 -122
- data/crates/itsi_server/src/body_proxy/mod.rs +0 -2
- data/crates/itsi_server/src/request/itsi_request.rs +0 -305
- data/crates/itsi_server/src/request/mod.rs +0 -1
- data/crates/itsi_server/src/response/mod.rs +0 -1
- data/crates/itsi_server/src/server/itsi_server.rs +0 -294
- data/gems/scheduler/ext/itsi_error/Cargo.lock +0 -368
- data/gems/scheduler/ext/itsi_error/Cargo.toml +0 -11
- data/gems/scheduler/ext/itsi_error/src/from.rs +0 -68
- data/gems/scheduler/ext/itsi_error/src/lib.rs +0 -24
- data/gems/scheduler/ext/itsi_instrument_entry/Cargo.toml +0 -15
- data/gems/scheduler/ext/itsi_instrument_entry/src/lib.rs +0 -31
- data/gems/scheduler/ext/itsi_rb_helpers/Cargo.lock +0 -355
- data/gems/scheduler/ext/itsi_rb_helpers/Cargo.toml +0 -10
- data/gems/scheduler/ext/itsi_rb_helpers/src/heap_value.rs +0 -121
- data/gems/scheduler/ext/itsi_rb_helpers/src/lib.rs +0 -178
- data/gems/scheduler/ext/itsi_scheduler/Cargo.toml +0 -24
- data/gems/scheduler/ext/itsi_scheduler/extconf.rb +0 -6
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +0 -56
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +0 -44
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +0 -44
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler.rs +0 -308
- data/gems/scheduler/ext/itsi_scheduler/src/lib.rs +0 -38
- data/gems/scheduler/ext/itsi_server/Cargo.lock +0 -2956
- data/gems/scheduler/ext/itsi_server/Cargo.toml +0 -47
- data/gems/scheduler/ext/itsi_server/extconf.rb +0 -6
- data/gems/scheduler/ext/itsi_server/src/body_proxy/big_bytes.rs +0 -104
- data/gems/scheduler/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +0 -122
- data/gems/scheduler/ext/itsi_server/src/body_proxy/mod.rs +0 -2
- data/gems/scheduler/ext/itsi_server/src/env.rs +0 -43
- data/gems/scheduler/ext/itsi_server/src/lib.rs +0 -112
- data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +0 -305
- data/gems/scheduler/ext/itsi_server/src/request/mod.rs +0 -1
- data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +0 -357
- data/gems/scheduler/ext/itsi_server/src/response/mod.rs +0 -1
- data/gems/scheduler/ext/itsi_server/src/server/bind.rs +0 -170
- data/gems/scheduler/ext/itsi_server/src/server/bind_protocol.rs +0 -37
- data/gems/scheduler/ext/itsi_server/src/server/io_stream.rs +0 -104
- data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +0 -294
- data/gems/scheduler/ext/itsi_server/src/server/lifecycle_event.rs +0 -9
- data/gems/scheduler/ext/itsi_server/src/server/listener.rs +0 -318
- data/gems/scheduler/ext/itsi_server/src/server/mod.rs +0 -11
- data/gems/scheduler/ext/itsi_server/src/server/process_worker.rs +0 -196
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +0 -254
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/mod.rs +0 -27
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +0 -263
- data/gems/scheduler/ext/itsi_server/src/server/signal.rs +0 -77
- data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +0 -367
- data/gems/scheduler/ext/itsi_server/src/server/tls/locked_dir_cache.rs +0 -132
- data/gems/scheduler/ext/itsi_server/src/server/tls.rs +0 -265
- data/gems/scheduler/ext/itsi_tracing/Cargo.lock +0 -274
- data/gems/scheduler/ext/itsi_tracing/Cargo.toml +0 -16
- data/gems/scheduler/ext/itsi_tracing/src/lib.rs +0 -58
- data/gems/server/ext/itsi_error/Cargo.lock +0 -368
- data/gems/server/ext/itsi_error/Cargo.toml +0 -11
- data/gems/server/ext/itsi_error/src/from.rs +0 -68
- data/gems/server/ext/itsi_error/src/lib.rs +0 -24
- data/gems/server/ext/itsi_instrument_entry/Cargo.toml +0 -15
- data/gems/server/ext/itsi_instrument_entry/src/lib.rs +0 -31
- data/gems/server/ext/itsi_rb_helpers/Cargo.lock +0 -355
- data/gems/server/ext/itsi_rb_helpers/Cargo.toml +0 -10
- data/gems/server/ext/itsi_rb_helpers/src/heap_value.rs +0 -121
- data/gems/server/ext/itsi_rb_helpers/src/lib.rs +0 -178
- data/gems/server/ext/itsi_scheduler/Cargo.toml +0 -24
- data/gems/server/ext/itsi_scheduler/extconf.rb +0 -6
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +0 -56
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +0 -44
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +0 -44
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler.rs +0 -308
- data/gems/server/ext/itsi_scheduler/src/lib.rs +0 -38
- data/gems/server/ext/itsi_server/Cargo.lock +0 -2956
- data/gems/server/ext/itsi_server/Cargo.toml +0 -47
- data/gems/server/ext/itsi_server/extconf.rb +0 -6
- data/gems/server/ext/itsi_server/src/body_proxy/big_bytes.rs +0 -104
- data/gems/server/ext/itsi_server/src/body_proxy/mod.rs +0 -2
- data/gems/server/ext/itsi_server/src/env.rs +0 -43
- data/gems/server/ext/itsi_server/src/lib.rs +0 -112
- data/gems/server/ext/itsi_server/src/request/itsi_request.rs +0 -305
- data/gems/server/ext/itsi_server/src/request/mod.rs +0 -1
- data/gems/server/ext/itsi_server/src/response/itsi_response.rs +0 -357
- data/gems/server/ext/itsi_server/src/response/mod.rs +0 -1
- data/gems/server/ext/itsi_server/src/server/bind.rs +0 -170
- data/gems/server/ext/itsi_server/src/server/bind_protocol.rs +0 -37
- data/gems/server/ext/itsi_server/src/server/io_stream.rs +0 -104
- data/gems/server/ext/itsi_server/src/server/itsi_server.rs +0 -294
- data/gems/server/ext/itsi_server/src/server/lifecycle_event.rs +0 -9
- data/gems/server/ext/itsi_server/src/server/listener.rs +0 -318
- data/gems/server/ext/itsi_server/src/server/mod.rs +0 -11
- data/gems/server/ext/itsi_server/src/server/process_worker.rs +0 -196
- data/gems/server/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +0 -254
- data/gems/server/ext/itsi_server/src/server/serve_strategy/mod.rs +0 -27
- data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +0 -263
- data/gems/server/ext/itsi_server/src/server/signal.rs +0 -77
- data/gems/server/ext/itsi_server/src/server/thread_worker.rs +0 -367
- data/gems/server/ext/itsi_server/src/server/tls/locked_dir_cache.rs +0 -132
- data/gems/server/ext/itsi_server/src/server/tls.rs +0 -265
- data/gems/server/ext/itsi_tracing/Cargo.lock +0 -274
- data/gems/server/ext/itsi_tracing/Cargo.toml +0 -16
- data/gems/server/ext/itsi_tracing/src/lib.rs +0 -58
- data/gems/server/lib/itsi/stream_io.rb +0 -38
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/docker-setup.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-app-boot.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-deploy.sample +0 -14
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-proxy-reboot.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-app-boot.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-build.sample +0 -51
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-connect.sample +0 -47
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-deploy.sample +0 -109
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-proxy-reboot.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/secrets +0 -17
- data/sandbox/itsi_sandbox_rails/bin/dev +0 -2
- data/sandbox/itsi_sandbox_rails/bin/jobs +0 -6
- data/sandbox/itsi_sandbox_rails/bin/kamal +0 -27
- data/sandbox/itsi_sandbox_rails/bin/thrust +0 -5
- data/sandbox/itsi_sandbox_rails/config/cache.yml +0 -16
- data/sandbox/itsi_sandbox_rails/config/deploy.yml +0 -116
- data/sandbox/itsi_sandbox_rails/config/queue.yml +0 -18
- data/sandbox/itsi_sandbox_rails/config/recurring.yml +0 -10
- data/sandbox/itsi_sandbox_rails/db/cable_schema.rb +0 -11
- data/sandbox/itsi_sandbox_rails/db/cache_schema.rb +0 -14
- data/sandbox/itsi_sandbox_rails/db/queue_schema.rb +0 -129
- data/sandbox/itsi_sandbox_rails/public/400.html +0 -114
- data/sandbox/itsi_sandbox_rails/test/fixtures/posts.yml +0 -9
- data/sandbox/itsi_sandbox_rails/test/models/post_test.rb +0 -7
- /data/{sandbox/itsi_sandbox_rails/script/.keep → crates/_index.md} +0 -0
- /data/gems/server/lib/itsi/{index.html → server/default_app/index.html} +0 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
use super::listener::ListenerInfo;
|
2
|
+
use super::middleware_stack::CompressionAlgorithm;
|
3
|
+
use super::middleware_stack::MiddlewareLayer;
|
4
|
+
use super::request_job::RequestJob;
|
5
|
+
use super::serve_strategy::single_mode::RunningPhase;
|
6
|
+
use super::types::HttpRequest;
|
7
|
+
use super::types::HttpResponse;
|
8
|
+
use crate::ruby_types::itsi_server::itsi_server_config::ServerParams;
|
9
|
+
use chrono;
|
10
|
+
use chrono::Local;
|
11
|
+
use either::Either;
|
12
|
+
use hyper::service::Service;
|
13
|
+
use itsi_error::ItsiError;
|
14
|
+
use regex::Regex;
|
15
|
+
use std::sync::OnceLock;
|
16
|
+
use std::{future::Future, ops::Deref, pin::Pin, sync::Arc};
|
17
|
+
use tokio::sync::watch::{self};
|
18
|
+
|
19
|
+
#[derive(Clone)]
|
20
|
+
pub struct ItsiService {
|
21
|
+
pub inner: Arc<IstiServiceInner>,
|
22
|
+
}
|
23
|
+
|
24
|
+
impl Deref for ItsiService {
|
25
|
+
type Target = Arc<IstiServiceInner>;
|
26
|
+
|
27
|
+
fn deref(&self) -> &Self::Target {
|
28
|
+
&self.inner
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
pub struct IstiServiceInner {
|
33
|
+
pub sender: async_channel::Sender<RequestJob>,
|
34
|
+
pub server_params: Arc<ServerParams>,
|
35
|
+
pub listener: Arc<ListenerInfo>,
|
36
|
+
pub addr: String,
|
37
|
+
pub shutdown_channel: watch::Receiver<RunningPhase>,
|
38
|
+
}
|
39
|
+
|
40
|
+
#[derive(Clone)]
|
41
|
+
pub struct RequestContext {
|
42
|
+
inner: Arc<RequestContextInner>,
|
43
|
+
}
|
44
|
+
|
45
|
+
impl Deref for RequestContext {
|
46
|
+
type Target = Arc<RequestContextInner>;
|
47
|
+
|
48
|
+
fn deref(&self) -> &Self::Target {
|
49
|
+
&self.inner
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
impl Deref for RequestContextInner {
|
54
|
+
type Target = ItsiService;
|
55
|
+
|
56
|
+
fn deref(&self) -> &Self::Target {
|
57
|
+
&self.service
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
pub struct RequestContextInner {
|
62
|
+
pub request_id: i128,
|
63
|
+
pub service: ItsiService,
|
64
|
+
pub matching_pattern: Option<Arc<Regex>>,
|
65
|
+
pub compression_method: OnceLock<CompressionAlgorithm>,
|
66
|
+
pub origin: OnceLock<Option<String>>,
|
67
|
+
pub start_time: chrono::DateTime<chrono::Utc>,
|
68
|
+
pub request: Option<Arc<HttpRequest>>,
|
69
|
+
pub request_start_time: OnceLock<chrono::DateTime<Local>>,
|
70
|
+
pub if_none_match: OnceLock<Option<String>>,
|
71
|
+
pub etag_value: OnceLock<Option<String>>,
|
72
|
+
}
|
73
|
+
|
74
|
+
impl RequestContext {
|
75
|
+
fn new(service: ItsiService, matching_pattern: Option<Arc<Regex>>) -> Self {
|
76
|
+
RequestContext {
|
77
|
+
inner: Arc::new(RequestContextInner {
|
78
|
+
request_id: rand::random::<i128>(),
|
79
|
+
service,
|
80
|
+
matching_pattern,
|
81
|
+
compression_method: OnceLock::new(),
|
82
|
+
origin: OnceLock::new(),
|
83
|
+
start_time: chrono::Utc::now(),
|
84
|
+
request: None,
|
85
|
+
request_start_time: OnceLock::new(),
|
86
|
+
if_none_match: OnceLock::new(),
|
87
|
+
etag_value: OnceLock::new(),
|
88
|
+
}),
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
pub fn set_compression_method(&self, method: CompressionAlgorithm) {
|
93
|
+
self.inner.compression_method.set(method).unwrap();
|
94
|
+
}
|
95
|
+
|
96
|
+
pub fn set_origin(&self, origin: Option<String>) {
|
97
|
+
self.inner.origin.set(origin).unwrap();
|
98
|
+
}
|
99
|
+
|
100
|
+
pub fn set_if_none_match(&self, value: Option<String>) {
|
101
|
+
self.inner.if_none_match.set(value).unwrap();
|
102
|
+
}
|
103
|
+
|
104
|
+
pub fn get_if_none_match(&self) -> Option<String> {
|
105
|
+
self.inner.if_none_match.get().cloned().flatten()
|
106
|
+
}
|
107
|
+
|
108
|
+
pub fn request_id(&self) -> String {
|
109
|
+
self.inner.request_id.to_string()
|
110
|
+
}
|
111
|
+
|
112
|
+
pub fn track_start_time(&self) {
|
113
|
+
self.inner
|
114
|
+
.request_start_time
|
115
|
+
.get_or_init(chrono::Local::now);
|
116
|
+
}
|
117
|
+
|
118
|
+
pub fn start_time(&self) -> Option<chrono::DateTime<Local>> {
|
119
|
+
self.inner.request_start_time.get().cloned()
|
120
|
+
}
|
121
|
+
|
122
|
+
pub fn get_response_time(&self) -> Option<chrono::TimeDelta> {
|
123
|
+
self.inner
|
124
|
+
.request_start_time
|
125
|
+
.get()
|
126
|
+
.map(|instant| Local::now() - instant)
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
impl Service<HttpRequest> for ItsiService {
|
131
|
+
type Response = HttpResponse;
|
132
|
+
type Error = ItsiError;
|
133
|
+
type Future = Pin<Box<dyn Future<Output = itsi_error::Result<HttpResponse>> + Send>>;
|
134
|
+
|
135
|
+
// This is called once per incoming Request.
|
136
|
+
fn call(&self, req: HttpRequest) -> Self::Future {
|
137
|
+
let params = self.server_params.clone();
|
138
|
+
let self_clone = self.clone();
|
139
|
+
Box::pin(async move {
|
140
|
+
let mut req = req;
|
141
|
+
let mut resp: Option<HttpResponse> = None;
|
142
|
+
let (stack, matching_pattern) = params.middleware.get().unwrap().stack_for(&req);
|
143
|
+
let mut context = RequestContext::new(self_clone, matching_pattern);
|
144
|
+
let mut depth = 0;
|
145
|
+
for (index, elm) in stack.iter().enumerate() {
|
146
|
+
match elm.before(req, &mut context).await {
|
147
|
+
Ok(Either::Left(r)) => req = r,
|
148
|
+
Ok(Either::Right(r)) => {
|
149
|
+
resp = Some(r);
|
150
|
+
depth = index;
|
151
|
+
break;
|
152
|
+
}
|
153
|
+
Err(e) => return Err(e.into()),
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
let mut resp = match resp {
|
158
|
+
Some(r) => r,
|
159
|
+
None => {
|
160
|
+
return Err(ItsiError::InternalServerError(
|
161
|
+
"No response returned from middleware stack".to_string(),
|
162
|
+
))
|
163
|
+
}
|
164
|
+
};
|
165
|
+
for elm in stack.iter().rev().skip(stack.len() - depth - 1) {
|
166
|
+
resp = elm.after(resp, &mut context).await;
|
167
|
+
}
|
168
|
+
|
169
|
+
Ok(resp)
|
170
|
+
})
|
171
|
+
}
|
172
|
+
}
|
@@ -6,7 +6,9 @@ use super::tls::ItsiTlsAcceptor;
|
|
6
6
|
use itsi_error::{ItsiError, Result};
|
7
7
|
use itsi_tracing::info;
|
8
8
|
use socket2::{Domain, Protocol, Socket, Type};
|
9
|
+
use std::fmt::Display;
|
9
10
|
use std::net::{IpAddr, SocketAddr, TcpListener};
|
11
|
+
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
10
12
|
use std::sync::Arc;
|
11
13
|
use std::{os::unix::net::UnixListener, path::PathBuf};
|
12
14
|
use tokio::net::TcpListener as TokioTcpListener;
|
@@ -241,26 +243,104 @@ impl std::fmt::Display for SockAddr {
|
|
241
243
|
}
|
242
244
|
}
|
243
245
|
}
|
246
|
+
impl Display for Listener {
|
247
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
248
|
+
match self {
|
249
|
+
Listener::Tcp(listener) | Listener::TcpTls((listener, _)) => write!(
|
250
|
+
f,
|
251
|
+
"{}",
|
252
|
+
listener
|
253
|
+
.local_addr()
|
254
|
+
.map(|addr| addr.to_string())
|
255
|
+
.unwrap_or_else(|_| "".to_string())
|
256
|
+
),
|
257
|
+
|
258
|
+
Listener::Unix(listener) | Listener::UnixTls((listener, _)) => write!(
|
259
|
+
f,
|
260
|
+
"{}",
|
261
|
+
listener
|
262
|
+
.local_addr()
|
263
|
+
.map(|addr| addr
|
264
|
+
.as_pathname()
|
265
|
+
.map(|path| path.to_str().unwrap_or("").to_owned())
|
266
|
+
.unwrap_or_default())
|
267
|
+
.unwrap_or_else(|_| "".to_string())
|
268
|
+
),
|
269
|
+
}
|
270
|
+
}
|
271
|
+
}
|
244
272
|
|
245
273
|
impl Listener {
|
246
|
-
pub fn
|
274
|
+
pub fn into_tokio_listener(self) -> TokioListener {
|
247
275
|
match self {
|
248
|
-
Listener::Tcp(listener) =>
|
249
|
-
TokioTcpListener::from_std(
|
250
|
-
|
276
|
+
Listener::Tcp(listener) => {
|
277
|
+
TokioListener::Tcp(TokioTcpListener::from_std(listener).unwrap())
|
278
|
+
}
|
251
279
|
Listener::TcpTls((listener, acceptor)) => TokioListener::TcpTls(
|
252
|
-
TokioTcpListener::from_std(
|
280
|
+
TokioTcpListener::from_std(listener).unwrap(),
|
253
281
|
acceptor.clone(),
|
254
282
|
),
|
255
|
-
Listener::Unix(listener) =>
|
256
|
-
TokioUnixListener::from_std(
|
257
|
-
|
283
|
+
Listener::Unix(listener) => {
|
284
|
+
TokioListener::Unix(TokioUnixListener::from_std(listener).unwrap())
|
285
|
+
}
|
258
286
|
Listener::UnixTls((listener, acceptor)) => TokioListener::UnixTls(
|
259
|
-
TokioUnixListener::from_std(
|
287
|
+
TokioUnixListener::from_std(listener).unwrap(),
|
260
288
|
acceptor.clone(),
|
261
289
|
),
|
262
290
|
}
|
263
291
|
}
|
292
|
+
|
293
|
+
/// Handover information when using exec to hand over the listener to a replacement process.
|
294
|
+
pub fn handover(&self) -> Result<(String, i32)> {
|
295
|
+
match self {
|
296
|
+
Listener::Tcp(listener) => {
|
297
|
+
let addr = listener.local_addr()?;
|
298
|
+
Ok((
|
299
|
+
format!("tcp://{}:{}", addr.ip().to_canonical(), addr.port()),
|
300
|
+
listener.as_raw_fd(),
|
301
|
+
))
|
302
|
+
}
|
303
|
+
Listener::TcpTls((listener, _)) => {
|
304
|
+
let addr = listener.local_addr()?;
|
305
|
+
Ok((
|
306
|
+
format!("tcp://{}:{}", addr.ip().to_canonical(), addr.port()),
|
307
|
+
listener.as_raw_fd(),
|
308
|
+
))
|
309
|
+
}
|
310
|
+
Listener::Unix(listener) => {
|
311
|
+
let addr = listener.local_addr()?;
|
312
|
+
Ok((
|
313
|
+
format!("unix://{}", addr.as_pathname().unwrap().to_str().unwrap()),
|
314
|
+
listener.as_raw_fd(),
|
315
|
+
))
|
316
|
+
}
|
317
|
+
Listener::UnixTls((listener, _)) => {
|
318
|
+
let addr = listener.local_addr()?;
|
319
|
+
Ok((
|
320
|
+
format!("unix://{}", addr.as_pathname().unwrap().to_str().unwrap()),
|
321
|
+
listener.as_raw_fd(),
|
322
|
+
))
|
323
|
+
}
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
pub fn inherit_fd(bind: Bind, fd: RawFd) -> Result<Self> {
|
328
|
+
let bound = match bind.address {
|
329
|
+
BindAddress::Ip(_) => match bind.protocol {
|
330
|
+
BindProtocol::Http => Listener::Tcp(revive_tcp_socket(fd)?),
|
331
|
+
BindProtocol::Https => {
|
332
|
+
let tcp_listener = revive_tcp_socket(fd)?;
|
333
|
+
Listener::TcpTls((tcp_listener, bind.tls_config.unwrap()))
|
334
|
+
}
|
335
|
+
_ => unreachable!(),
|
336
|
+
},
|
337
|
+
BindAddress::UnixSocket(_) => match bind.tls_config {
|
338
|
+
Some(tls_config) => Listener::UnixTls((revive_unix_socket(fd)?, tls_config)),
|
339
|
+
None => Listener::Unix(revive_unix_socket(fd)?),
|
340
|
+
},
|
341
|
+
};
|
342
|
+
Ok(bound)
|
343
|
+
}
|
264
344
|
}
|
265
345
|
|
266
346
|
impl TryFrom<Bind> for Listener {
|
@@ -285,6 +365,27 @@ impl TryFrom<Bind> for Listener {
|
|
285
365
|
}
|
286
366
|
}
|
287
367
|
|
368
|
+
fn revive_tcp_socket(fd: RawFd) -> Result<TcpListener> {
|
369
|
+
let socket = unsafe { Socket::from_raw_fd(fd) };
|
370
|
+
socket.set_reuse_port(true).ok();
|
371
|
+
socket.set_reuse_address(true).ok();
|
372
|
+
socket.set_nonblocking(true).ok();
|
373
|
+
socket.set_nodelay(true).ok();
|
374
|
+
socket.set_recv_buffer_size(262_144).ok();
|
375
|
+
socket.set_cloexec(true)?;
|
376
|
+
socket.listen(1024)?;
|
377
|
+
Ok(socket.into())
|
378
|
+
}
|
379
|
+
|
380
|
+
fn revive_unix_socket(fd: RawFd) -> Result<UnixListener> {
|
381
|
+
let socket = unsafe { Socket::from_raw_fd(fd) };
|
382
|
+
socket.set_nonblocking(true).ok();
|
383
|
+
socket.listen(1024)?;
|
384
|
+
socket.set_cloexec(true)?;
|
385
|
+
|
386
|
+
Ok(socket.into())
|
387
|
+
}
|
388
|
+
|
288
389
|
fn connect_tcp_socket(addr: IpAddr, port: u16) -> Result<TcpListener> {
|
289
390
|
let domain = match addr {
|
290
391
|
IpAddr::V4(_) => Domain::IPV4,
|
@@ -297,7 +398,7 @@ fn connect_tcp_socket(addr: IpAddr, port: u16) -> Result<TcpListener> {
|
|
297
398
|
socket.set_nonblocking(true).ok();
|
298
399
|
socket.set_nodelay(true).ok();
|
299
400
|
socket.set_recv_buffer_size(262_144).ok();
|
300
|
-
|
401
|
+
socket.set_only_v6(false).ok();
|
301
402
|
socket.bind(&socket_address.into())?;
|
302
403
|
socket.listen(1024)?;
|
303
404
|
Ok(socket.into())
|
@@ -310,7 +411,6 @@ fn connect_unix_socket(path: &PathBuf) -> Result<UnixListener> {
|
|
310
411
|
|
311
412
|
let socket_address = socket2::SockAddr::unix(path)?;
|
312
413
|
|
313
|
-
info!("Binding to {:?}", path);
|
314
414
|
socket.bind(&socket_address)?;
|
315
415
|
socket.listen(1024)?;
|
316
416
|
|
@@ -0,0 +1,153 @@
|
|
1
|
+
use super::middlewares::*;
|
2
|
+
use crate::server::{
|
3
|
+
itsi_service::RequestContext,
|
4
|
+
types::{HttpRequest, HttpResponse},
|
5
|
+
};
|
6
|
+
use async_trait::async_trait;
|
7
|
+
use either::Either;
|
8
|
+
use magnus::error::Result;
|
9
|
+
use std::cmp::Ordering;
|
10
|
+
|
11
|
+
#[derive(Debug)]
|
12
|
+
pub enum Middleware {
|
13
|
+
AllowList(AllowList),
|
14
|
+
AuthAPIKey(AuthAPIKey),
|
15
|
+
AuthBasic(AuthBasic),
|
16
|
+
AuthJwt(Box<AuthJwt>),
|
17
|
+
CacheControl(CacheControl),
|
18
|
+
Compression(Compression),
|
19
|
+
Cors(Box<Cors>),
|
20
|
+
DenyList(DenyList),
|
21
|
+
ETag(ETag),
|
22
|
+
IntrusionProtection(IntrusionProtection),
|
23
|
+
LogRequests(LogRequests),
|
24
|
+
Proxy(Proxy),
|
25
|
+
RateLimit(RateLimit),
|
26
|
+
Redirect(Redirect),
|
27
|
+
RequestHeaders(RequestHeaders),
|
28
|
+
ResponseHeaders(ResponseHeaders),
|
29
|
+
RubyApp(RubyApp),
|
30
|
+
StaticAssets(StaticAssets),
|
31
|
+
}
|
32
|
+
|
33
|
+
#[async_trait]
|
34
|
+
impl MiddlewareLayer for Middleware {
|
35
|
+
/// Called just once, to initialize the middleware state.
|
36
|
+
async fn initialize(&self) -> Result<()> {
|
37
|
+
match self {
|
38
|
+
Middleware::DenyList(filter) => filter.initialize().await,
|
39
|
+
Middleware::AllowList(filter) => filter.initialize().await,
|
40
|
+
Middleware::AuthBasic(filter) => filter.initialize().await,
|
41
|
+
Middleware::AuthJwt(filter) => filter.initialize().await,
|
42
|
+
Middleware::AuthAPIKey(filter) => filter.initialize().await,
|
43
|
+
Middleware::IntrusionProtection(filter) => filter.initialize().await,
|
44
|
+
Middleware::RateLimit(filter) => filter.initialize().await,
|
45
|
+
Middleware::RequestHeaders(filter) => filter.initialize().await,
|
46
|
+
Middleware::ResponseHeaders(filter) => filter.initialize().await,
|
47
|
+
Middleware::CacheControl(filter) => filter.initialize().await,
|
48
|
+
Middleware::Cors(filter) => filter.initialize().await,
|
49
|
+
Middleware::ETag(filter) => filter.initialize().await,
|
50
|
+
Middleware::StaticAssets(filter) => filter.initialize().await,
|
51
|
+
Middleware::Compression(filter) => filter.initialize().await,
|
52
|
+
Middleware::LogRequests(filter) => filter.initialize().await,
|
53
|
+
Middleware::Redirect(filter) => filter.initialize().await,
|
54
|
+
Middleware::Proxy(filter) => filter.initialize().await,
|
55
|
+
Middleware::RubyApp(filter) => filter.initialize().await,
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
async fn before(
|
60
|
+
&self,
|
61
|
+
req: HttpRequest,
|
62
|
+
context: &mut RequestContext,
|
63
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
64
|
+
match self {
|
65
|
+
Middleware::DenyList(filter) => filter.before(req, context).await,
|
66
|
+
Middleware::AllowList(filter) => filter.before(req, context).await,
|
67
|
+
Middleware::AuthBasic(filter) => filter.before(req, context).await,
|
68
|
+
Middleware::AuthJwt(filter) => filter.before(req, context).await,
|
69
|
+
Middleware::AuthAPIKey(filter) => filter.before(req, context).await,
|
70
|
+
Middleware::IntrusionProtection(filter) => filter.before(req, context).await,
|
71
|
+
Middleware::RequestHeaders(filter) => filter.before(req, context).await,
|
72
|
+
Middleware::ResponseHeaders(filter) => filter.before(req, context).await,
|
73
|
+
Middleware::RateLimit(filter) => filter.before(req, context).await,
|
74
|
+
Middleware::CacheControl(filter) => filter.before(req, context).await,
|
75
|
+
Middleware::Cors(filter) => filter.before(req, context).await,
|
76
|
+
Middleware::ETag(filter) => filter.before(req, context).await,
|
77
|
+
Middleware::StaticAssets(filter) => filter.before(req, context).await,
|
78
|
+
Middleware::Compression(filter) => filter.before(req, context).await,
|
79
|
+
Middleware::LogRequests(filter) => filter.before(req, context).await,
|
80
|
+
Middleware::Redirect(filter) => filter.before(req, context).await,
|
81
|
+
Middleware::Proxy(filter) => filter.before(req, context).await,
|
82
|
+
Middleware::RubyApp(filter) => filter.before(req, context).await,
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
async fn after(&self, res: HttpResponse, context: &mut RequestContext) -> HttpResponse {
|
87
|
+
match self {
|
88
|
+
Middleware::DenyList(filter) => filter.after(res, context).await,
|
89
|
+
Middleware::AllowList(filter) => filter.after(res, context).await,
|
90
|
+
Middleware::AuthBasic(filter) => filter.after(res, context).await,
|
91
|
+
Middleware::AuthJwt(filter) => filter.after(res, context).await,
|
92
|
+
Middleware::AuthAPIKey(filter) => filter.after(res, context).await,
|
93
|
+
Middleware::IntrusionProtection(filter) => filter.after(res, context).await,
|
94
|
+
Middleware::RateLimit(filter) => filter.after(res, context).await,
|
95
|
+
Middleware::RequestHeaders(filter) => filter.after(res, context).await,
|
96
|
+
Middleware::ResponseHeaders(filter) => filter.after(res, context).await,
|
97
|
+
Middleware::CacheControl(filter) => filter.after(res, context).await,
|
98
|
+
Middleware::Cors(filter) => filter.after(res, context).await,
|
99
|
+
Middleware::ETag(filter) => filter.after(res, context).await,
|
100
|
+
Middleware::StaticAssets(filter) => filter.after(res, context).await,
|
101
|
+
Middleware::Compression(filter) => filter.after(res, context).await,
|
102
|
+
Middleware::LogRequests(filter) => filter.after(res, context).await,
|
103
|
+
Middleware::Redirect(filter) => filter.after(res, context).await,
|
104
|
+
Middleware::Proxy(filter) => filter.after(res, context).await,
|
105
|
+
Middleware::RubyApp(filter) => filter.after(res, context).await,
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
impl Middleware {
|
111
|
+
fn variant_order(&self) -> usize {
|
112
|
+
match self {
|
113
|
+
Middleware::DenyList(_) => 0,
|
114
|
+
Middleware::AllowList(_) => 1,
|
115
|
+
Middleware::IntrusionProtection(_) => 2,
|
116
|
+
Middleware::Redirect(_) => 3,
|
117
|
+
Middleware::LogRequests(_) => 4,
|
118
|
+
Middleware::CacheControl(_) => 5,
|
119
|
+
Middleware::RequestHeaders(_) => 6,
|
120
|
+
Middleware::ResponseHeaders(_) => 7,
|
121
|
+
Middleware::AuthBasic(_) => 8,
|
122
|
+
Middleware::AuthJwt(_) => 9,
|
123
|
+
Middleware::AuthAPIKey(_) => 10,
|
124
|
+
Middleware::RateLimit(_) => 11,
|
125
|
+
Middleware::ETag(_) => 12,
|
126
|
+
Middleware::Compression(_) => 13,
|
127
|
+
Middleware::Proxy(_) => 14,
|
128
|
+
Middleware::Cors(_) => 15,
|
129
|
+
Middleware::StaticAssets(_) => 16,
|
130
|
+
Middleware::RubyApp(_) => 17,
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
impl PartialEq for Middleware {
|
136
|
+
fn eq(&self, other: &Self) -> bool {
|
137
|
+
self.variant_order() == other.variant_order()
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
impl Eq for Middleware {}
|
142
|
+
|
143
|
+
impl PartialOrd for Middleware {
|
144
|
+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
145
|
+
Some(self.variant_order().cmp(&other.variant_order()))
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
impl Ord for Middleware {
|
150
|
+
fn cmp(&self, other: &Self) -> Ordering {
|
151
|
+
self.variant_order().cmp(&other.variant_order())
|
152
|
+
}
|
153
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
use super::{ErrorResponse, FromValue, MiddlewareLayer};
|
2
|
+
use crate::server::{
|
3
|
+
itsi_service::RequestContext,
|
4
|
+
types::{HttpRequest, HttpResponse},
|
5
|
+
};
|
6
|
+
use async_trait::async_trait;
|
7
|
+
use either::Either;
|
8
|
+
use itsi_error::ItsiError;
|
9
|
+
use magnus::error::Result;
|
10
|
+
use regex::RegexSet;
|
11
|
+
use serde::Deserialize;
|
12
|
+
use std::sync::OnceLock;
|
13
|
+
|
14
|
+
#[derive(Debug, Clone, Deserialize)]
|
15
|
+
pub struct AllowList {
|
16
|
+
#[serde(skip_deserializing)]
|
17
|
+
pub allowed_ips: OnceLock<RegexSet>,
|
18
|
+
pub allowed_patterns: Vec<String>,
|
19
|
+
pub error_response: ErrorResponse,
|
20
|
+
}
|
21
|
+
|
22
|
+
#[async_trait]
|
23
|
+
impl MiddlewareLayer for AllowList {
|
24
|
+
async fn initialize(&self) -> Result<()> {
|
25
|
+
let allowed_ips = RegexSet::new(&self.allowed_patterns).map_err(ItsiError::default)?;
|
26
|
+
self.allowed_ips
|
27
|
+
.set(allowed_ips)
|
28
|
+
.map_err(|e| ItsiError::default(format!("Failed to set allowed IPs: {:?}", e)))?;
|
29
|
+
Ok(())
|
30
|
+
}
|
31
|
+
|
32
|
+
async fn before(
|
33
|
+
&self,
|
34
|
+
req: HttpRequest,
|
35
|
+
context: &mut RequestContext,
|
36
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
37
|
+
if let Some(allowed_ips) = self.allowed_ips.get() {
|
38
|
+
if !allowed_ips.is_match(&context.addr) {
|
39
|
+
return Ok(Either::Right(
|
40
|
+
self.error_response.to_http_response(&req).await,
|
41
|
+
));
|
42
|
+
}
|
43
|
+
}
|
44
|
+
Ok(Either::Left(req))
|
45
|
+
}
|
46
|
+
}
|
47
|
+
impl FromValue for AllowList {}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
use crate::server::{
|
2
|
+
itsi_service::RequestContext,
|
3
|
+
types::{HttpRequest, HttpResponse, RequestExt},
|
4
|
+
};
|
5
|
+
|
6
|
+
use super::{error_response::ErrorResponse, token_source::TokenSource, FromValue, MiddlewareLayer};
|
7
|
+
|
8
|
+
use async_trait::async_trait;
|
9
|
+
use either::Either;
|
10
|
+
use magnus::error::Result;
|
11
|
+
use serde::Deserialize;
|
12
|
+
|
13
|
+
/// A simple API key filter.
|
14
|
+
/// The API key can be given inside the header or a query string
|
15
|
+
/// Keys are validated against a list of allowed key values (Changing these requires a restart)
|
16
|
+
///
|
17
|
+
#[derive(Debug, Clone, Deserialize)]
|
18
|
+
pub struct AuthAPIKey {
|
19
|
+
pub valid_keys: Vec<String>,
|
20
|
+
pub token_source: TokenSource,
|
21
|
+
pub error_response: ErrorResponse,
|
22
|
+
}
|
23
|
+
|
24
|
+
#[async_trait]
|
25
|
+
impl MiddlewareLayer for AuthAPIKey {
|
26
|
+
async fn before(
|
27
|
+
&self,
|
28
|
+
req: HttpRequest,
|
29
|
+
_context: &mut RequestContext,
|
30
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
31
|
+
let submitted_value = match &self.token_source {
|
32
|
+
TokenSource::Header { name, prefix } => {
|
33
|
+
if let Some(header) = req.header(name) {
|
34
|
+
if let Some(prefix) = prefix {
|
35
|
+
Some(header.strip_prefix(prefix).unwrap_or("").trim_ascii())
|
36
|
+
} else {
|
37
|
+
Some(header.trim_ascii())
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
None
|
41
|
+
}
|
42
|
+
}
|
43
|
+
TokenSource::Query(query_name) => req.query_param(query_name),
|
44
|
+
};
|
45
|
+
if !self
|
46
|
+
.valid_keys
|
47
|
+
.iter()
|
48
|
+
.any(|key| submitted_value.is_some_and(|sv| sv == key))
|
49
|
+
{
|
50
|
+
Ok(Either::Right(
|
51
|
+
self.error_response.to_http_response(&req).await,
|
52
|
+
))
|
53
|
+
} else {
|
54
|
+
Ok(Either::Left(req))
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
impl FromValue for AuthAPIKey {}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
use async_trait::async_trait;
|
2
|
+
use base64::{engine::general_purpose, Engine};
|
3
|
+
use bytes::Bytes;
|
4
|
+
use either::Either;
|
5
|
+
use http::{Response, StatusCode};
|
6
|
+
use http_body_util::{combinators::BoxBody, Full};
|
7
|
+
use magnus::error::Result;
|
8
|
+
use serde::{Deserialize, Serialize};
|
9
|
+
use std::collections::HashMap;
|
10
|
+
use std::str;
|
11
|
+
|
12
|
+
use crate::server::{
|
13
|
+
itsi_service::RequestContext,
|
14
|
+
types::{HttpRequest, HttpResponse, RequestExt},
|
15
|
+
};
|
16
|
+
|
17
|
+
use super::{FromValue, MiddlewareLayer};
|
18
|
+
|
19
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
20
|
+
pub struct AuthBasic {
|
21
|
+
pub realm: String,
|
22
|
+
/// Maps usernames to passwords.
|
23
|
+
pub credential_pairs: HashMap<String, String>,
|
24
|
+
}
|
25
|
+
|
26
|
+
impl AuthBasic {
|
27
|
+
fn basic_auth_failed_response(&self) -> HttpResponse {
|
28
|
+
Response::builder()
|
29
|
+
.status(StatusCode::UNAUTHORIZED)
|
30
|
+
.header(
|
31
|
+
"WWW-Authenticate",
|
32
|
+
format!("Basic realm=\"{}\"", self.realm),
|
33
|
+
)
|
34
|
+
.body(BoxBody::new(Full::new(Bytes::from("Unauthorized"))))
|
35
|
+
.unwrap()
|
36
|
+
}
|
37
|
+
}
|
38
|
+
#[async_trait]
|
39
|
+
impl MiddlewareLayer for AuthBasic {
|
40
|
+
async fn before(
|
41
|
+
&self,
|
42
|
+
req: HttpRequest,
|
43
|
+
_context: &mut RequestContext,
|
44
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
45
|
+
// Retrieve the Authorization header.
|
46
|
+
let auth_header = req.header("Authorization");
|
47
|
+
|
48
|
+
if !auth_header.is_some_and(|header| header.starts_with("Basic ")) {
|
49
|
+
return Ok(Either::Right(self.basic_auth_failed_response()));
|
50
|
+
}
|
51
|
+
|
52
|
+
let auth_header = auth_header.unwrap();
|
53
|
+
|
54
|
+
let encoded_credentials = &auth_header["Basic ".len()..];
|
55
|
+
let decoded = match general_purpose::STANDARD.decode(encoded_credentials) {
|
56
|
+
Ok(bytes) => bytes,
|
57
|
+
Err(_) => {
|
58
|
+
return Ok(Either::Right(self.basic_auth_failed_response()));
|
59
|
+
}
|
60
|
+
};
|
61
|
+
|
62
|
+
let decoded_str = match str::from_utf8(&decoded) {
|
63
|
+
Ok(s) => s,
|
64
|
+
Err(_) => {
|
65
|
+
return Ok(Either::Right(self.basic_auth_failed_response()));
|
66
|
+
}
|
67
|
+
};
|
68
|
+
|
69
|
+
let mut parts = decoded_str.splitn(2, ':');
|
70
|
+
let username = parts.next().unwrap_or("");
|
71
|
+
let password = parts.next().unwrap_or("");
|
72
|
+
|
73
|
+
match self.credential_pairs.get(username) {
|
74
|
+
Some(expected_password) if expected_password == password => Ok(Either::Left(req)),
|
75
|
+
_ => {
|
76
|
+
return Ok(Either::Right(self.basic_auth_failed_response()));
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
impl FromValue for AuthBasic {}
|