itsi-server 0.1.1 → 0.1.18
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.
Potentially problematic release.
This version of itsi-server might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +3937 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +141 -46
- data/ext/itsi_error/Cargo.toml +3 -0
- data/ext/itsi_error/src/lib.rs +98 -24
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +72 -14
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/default_responses/html/401.html +68 -0
- data/ext/itsi_server/src/default_responses/html/403.html +68 -0
- data/ext/itsi_server/src/default_responses/html/404.html +68 -0
- data/ext/itsi_server/src/default_responses/html/413.html +71 -0
- data/ext/itsi_server/src/default_responses/html/429.html +68 -0
- data/ext/itsi_server/src/default_responses/html/500.html +71 -0
- data/ext/itsi_server/src/default_responses/html/502.html +71 -0
- data/ext/itsi_server/src/default_responses/html/503.html +68 -0
- data/ext/itsi_server/src/default_responses/html/504.html +69 -0
- data/ext/itsi_server/src/default_responses/html/index.html +238 -0
- data/ext/itsi_server/src/default_responses/json/401.json +6 -0
- data/ext/itsi_server/src/default_responses/json/403.json +6 -0
- data/ext/itsi_server/src/default_responses/json/404.json +6 -0
- data/ext/itsi_server/src/default_responses/json/413.json +6 -0
- data/ext/itsi_server/src/default_responses/json/429.json +6 -0
- data/ext/itsi_server/src/default_responses/json/500.json +6 -0
- data/ext/itsi_server/src/default_responses/json/502.json +6 -0
- data/ext/itsi_server/src/default_responses/json/503.json +6 -0
- data/ext/itsi_server/src/default_responses/json/504.json +6 -0
- data/ext/itsi_server/src/default_responses/mod.rs +11 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +132 -40
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +143 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +345 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +391 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +375 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +83 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
- data/ext/itsi_server/src/server/binds/bind.rs +201 -0
- data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/binds/listener.rs +432 -0
- data/ext/itsi_server/src/server/binds/mod.rs +4 -0
- data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/binds/tls.rs +270 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/http_message_types.rs +97 -0
- data/ext/itsi_server/src/server/io_stream.rs +105 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +165 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +56 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +87 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +86 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +285 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +142 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +289 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +292 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +157 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +201 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +87 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +414 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +131 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +44 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +36 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +180 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +163 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +347 -0
- data/ext/itsi_server/src/server/mod.rs +12 -5
- data/ext/itsi_server/src/server/process_worker.rs +247 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +342 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +76 -0
- data/ext/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/ext/itsi_server/src/server/thread_worker.rs +475 -0
- data/ext/itsi_server/src/services/cache_store.rs +74 -0
- data/ext/itsi_server/src/services/itsi_http_service.rs +239 -0
- data/ext/itsi_server/src/services/mime_types.rs +1416 -0
- data/ext/itsi_server/src/services/mod.rs +6 -0
- data/ext/itsi_server/src/services/password_hasher.rs +83 -0
- data/ext/itsi_server/src/services/rate_limiter.rs +569 -0
- data/ext/itsi_server/src/services/static_file_server.rs +1324 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +315 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
- data/lib/itsi/http_request.rb +186 -0
- data/lib/itsi/http_response.rb +41 -0
- data/lib/itsi/passfile.rb +109 -0
- data/lib/itsi/server/config/dsl.rb +565 -0
- data/lib/itsi/server/config.rb +166 -0
- data/lib/itsi/server/default_app/default_app.rb +34 -0
- data/lib/itsi/server/default_app/index.html +115 -0
- data/lib/itsi/server/default_config/Itsi-rackup.rb +119 -0
- data/lib/itsi/server/default_config/Itsi.rb +107 -0
- data/lib/itsi/server/grpc/grpc_call.rb +246 -0
- data/lib/itsi/server/grpc/grpc_interface.rb +100 -0
- data/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/route_tester.rb +107 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
- data/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
- data/lib/itsi/server/typed_handlers.rb +17 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +160 -9
- data/lib/itsi/standard_headers.rb +86 -0
- data/lib/ruby_lsp/itsi/addon.rb +111 -0
- data/lib/shell_completions/completions.rb +26 -0
- metadata +182 -25
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/bind.rs +0 -138
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/listener.rs +0 -218
- data/ext/itsi_server/src/server/tls.rs +0 -138
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,347 @@
|
|
1
|
+
mod middleware;
|
2
|
+
mod middlewares;
|
3
|
+
use http::header::{ACCEPT, CONTENT_TYPE, HOST};
|
4
|
+
use itsi_rb_helpers::HeapVal;
|
5
|
+
use magnus::{error::Result, value::ReprValue, RArray, RHash, Ruby, TryConvert, Value};
|
6
|
+
pub use middleware::Middleware;
|
7
|
+
pub use middlewares::*;
|
8
|
+
use regex::{Regex, RegexSet};
|
9
|
+
use std::{collections::HashMap, sync::Arc};
|
10
|
+
use tracing::debug;
|
11
|
+
|
12
|
+
use super::http_message_types::HttpRequest;
|
13
|
+
|
14
|
+
#[derive(Debug)]
|
15
|
+
pub struct MiddlewareSet {
|
16
|
+
pub route_set: RegexSet,
|
17
|
+
pub patterns: Vec<Arc<Regex>>,
|
18
|
+
pub stacks: HashMap<usize, MiddlewareStack>,
|
19
|
+
}
|
20
|
+
|
21
|
+
#[derive(Debug)]
|
22
|
+
pub struct MiddlewareStack {
|
23
|
+
layers: Vec<Middleware>,
|
24
|
+
methods: Option<Vec<StringMatch>>,
|
25
|
+
protocols: Option<Vec<StringMatch>>,
|
26
|
+
hosts: Option<Vec<StringMatch>>,
|
27
|
+
extensions: Option<Vec<StringMatch>>,
|
28
|
+
ports: Option<Vec<StringMatch>>,
|
29
|
+
content_types: Option<Vec<StringMatch>>,
|
30
|
+
accepts: Option<Vec<StringMatch>>,
|
31
|
+
}
|
32
|
+
|
33
|
+
#[derive(Debug)]
|
34
|
+
enum StringMatch {
|
35
|
+
Exact(String),
|
36
|
+
Wildcard(Regex),
|
37
|
+
}
|
38
|
+
|
39
|
+
impl StringMatch {
|
40
|
+
fn from_value(value: Value) -> Result<Self> {
|
41
|
+
let ruby = Ruby::get().unwrap();
|
42
|
+
if value.is_kind_of(ruby.class_regexp()) {
|
43
|
+
let src_str = value.funcall::<_, _, String>("source", ())?;
|
44
|
+
let regex = Regex::new(&src_str).map_err(|e| {
|
45
|
+
magnus::Error::new(
|
46
|
+
magnus::exception::standard_error(),
|
47
|
+
format!("Invalid regexp: {}", e),
|
48
|
+
)
|
49
|
+
})?;
|
50
|
+
Ok(StringMatch::Wildcard(regex))
|
51
|
+
} else {
|
52
|
+
Ok(StringMatch::Exact(value.to_string()))
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
fn matches(&self, value: &str) -> bool {
|
57
|
+
match self {
|
58
|
+
StringMatch::Exact(s) => s.eq_ignore_ascii_case(value),
|
59
|
+
StringMatch::Wildcard(re) => re.is_match(value),
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
impl MiddlewareStack {
|
65
|
+
pub fn matches(&self, request: &HttpRequest) -> bool {
|
66
|
+
if let Some(methods) = &self.methods {
|
67
|
+
let method = request.method().as_str();
|
68
|
+
if !methods.iter().any(|m| m.matches(method)) {
|
69
|
+
return false;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
if let (Some(protocols), Some(protocol)) = (&self.protocols, request.uri().scheme()) {
|
74
|
+
if !protocols.iter().any(|p| p.matches(protocol.as_str())) {
|
75
|
+
return false;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
if let (Some(hosts), Some(host)) = (&self.hosts, request.headers().get(HOST)) {
|
80
|
+
if let Ok(host) = host.to_str() {
|
81
|
+
if !hosts.iter().any(|d| d.matches(host)) {
|
82
|
+
return false;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
if let (Some(ports), Some(port)) = (&self.ports, request.uri().port()) {
|
88
|
+
if !ports.iter().any(|d| d.matches(port.as_str())) {
|
89
|
+
return false;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
if let Some(extensions) = &self.extensions {
|
94
|
+
let path = request.uri().path();
|
95
|
+
let segment = path.split('/').next_back().unwrap_or("");
|
96
|
+
let extension = segment.split('.').next_back().unwrap_or("");
|
97
|
+
let extension = if segment != extension { extension } else { "" };
|
98
|
+
if !extensions.iter().any(|e| e.matches(extension)) {
|
99
|
+
return false;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
if let Some(content_types) = &self.content_types {
|
104
|
+
if let Some(content_type) = request.headers().get(CONTENT_TYPE) {
|
105
|
+
if !content_types
|
106
|
+
.iter()
|
107
|
+
.any(|ct| ct.matches(content_type.to_str().unwrap_or("")))
|
108
|
+
{
|
109
|
+
return false;
|
110
|
+
}
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
if let Some(accepts) = &self.accepts {
|
115
|
+
if let Some(accept) = request.headers().get(ACCEPT) {
|
116
|
+
if !accepts
|
117
|
+
.iter()
|
118
|
+
.any(|a| a.matches(accept.to_str().unwrap_or("")))
|
119
|
+
{
|
120
|
+
return false;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
true
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
impl MiddlewareSet {
|
130
|
+
pub fn new(routes_raw: Option<HeapVal>) -> Result<Self> {
|
131
|
+
if let Some(routes_raw) = routes_raw {
|
132
|
+
let mut stacks = HashMap::new();
|
133
|
+
let mut routes = vec![];
|
134
|
+
for (index, route) in RArray::from_value(*routes_raw)
|
135
|
+
.ok_or(magnus::Error::new(
|
136
|
+
magnus::exception::standard_error(),
|
137
|
+
format!("Routes must be an array. Got {:?}", routes_raw),
|
138
|
+
))?
|
139
|
+
.into_iter()
|
140
|
+
.enumerate()
|
141
|
+
{
|
142
|
+
let route_hash: RHash = RHash::try_convert(route)?;
|
143
|
+
let route_raw = route_hash
|
144
|
+
.get("route")
|
145
|
+
.ok_or(magnus::Error::new(
|
146
|
+
magnus::exception::standard_error(),
|
147
|
+
"Route is missing :route key",
|
148
|
+
))?
|
149
|
+
.funcall::<_, _, String>("source", ())?;
|
150
|
+
let middleware =
|
151
|
+
RArray::from_value(route_hash.get("middleware").ok_or(magnus::Error::new(
|
152
|
+
magnus::exception::standard_error(),
|
153
|
+
"Route is missing middleware key",
|
154
|
+
))?)
|
155
|
+
.ok_or(magnus::Error::new(
|
156
|
+
magnus::exception::standard_error(),
|
157
|
+
format!("middleware must be an array. Got {:?}", routes_raw),
|
158
|
+
))?;
|
159
|
+
|
160
|
+
let mut layers = middleware
|
161
|
+
.into_iter()
|
162
|
+
.map(MiddlewareSet::parse_middleware)
|
163
|
+
.collect::<Result<Vec<_>>>()?;
|
164
|
+
routes.push(route_raw);
|
165
|
+
layers.sort();
|
166
|
+
stacks.insert(
|
167
|
+
index,
|
168
|
+
MiddlewareStack {
|
169
|
+
layers,
|
170
|
+
methods: extract_optional_match_array(route_hash, "methods")?,
|
171
|
+
protocols: extract_optional_match_array(route_hash, "protocols")?,
|
172
|
+
hosts: extract_optional_match_array(route_hash, "hosts")?,
|
173
|
+
extensions: extract_optional_match_array(route_hash, "extensions")?,
|
174
|
+
ports: extract_optional_match_array(route_hash, "ports")?,
|
175
|
+
content_types: extract_optional_match_array(route_hash, "content_types")?,
|
176
|
+
accepts: extract_optional_match_array(route_hash, "accepts")?,
|
177
|
+
},
|
178
|
+
);
|
179
|
+
}
|
180
|
+
Ok(Self {
|
181
|
+
route_set: RegexSet::new(&routes).map_err(|e| {
|
182
|
+
magnus::Error::new(
|
183
|
+
magnus::exception::standard_error(),
|
184
|
+
format!("Failed to create route set: {}", e),
|
185
|
+
)
|
186
|
+
})?,
|
187
|
+
patterns: routes
|
188
|
+
.into_iter()
|
189
|
+
.map(|r| Regex::new(&r))
|
190
|
+
.collect::<std::result::Result<Vec<Regex>, regex::Error>>()
|
191
|
+
.map_err(|e| {
|
192
|
+
magnus::Error::new(
|
193
|
+
magnus::exception::standard_error(),
|
194
|
+
format!("Failed to create route set: {}", e),
|
195
|
+
)
|
196
|
+
})?
|
197
|
+
.into_iter()
|
198
|
+
.map(Arc::new)
|
199
|
+
.collect(),
|
200
|
+
stacks,
|
201
|
+
})
|
202
|
+
} else {
|
203
|
+
Err(magnus::Error::new(
|
204
|
+
magnus::exception::standard_error(),
|
205
|
+
"Failed to create middleware stack",
|
206
|
+
))
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
pub fn stack_for(
|
211
|
+
&self,
|
212
|
+
request: &HttpRequest,
|
213
|
+
) -> Result<(&Vec<Middleware>, Option<Arc<Regex>>)> {
|
214
|
+
let binding = self.route_set.matches(request.uri().path());
|
215
|
+
let matches = binding.iter();
|
216
|
+
|
217
|
+
debug!(
|
218
|
+
"Matching request URI {:?} against self.route_set: {:?}",
|
219
|
+
request.uri().path(),
|
220
|
+
self.route_set
|
221
|
+
);
|
222
|
+
for index in matches {
|
223
|
+
let matching_pattern = self.patterns.get(index).cloned();
|
224
|
+
if let Some(stack) = self.stacks.get(&index) {
|
225
|
+
if stack.matches(request) {
|
226
|
+
return Ok((&stack.layers, matching_pattern));
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
230
|
+
debug!(
|
231
|
+
"Failed to match request URI {:?} to self.route_set: {:?}",
|
232
|
+
request.uri().path(),
|
233
|
+
self.route_set
|
234
|
+
);
|
235
|
+
Err(magnus::Error::new(
|
236
|
+
magnus::exception::standard_error(),
|
237
|
+
format!(
|
238
|
+
"No matching middleware stack found for request: {:?}",
|
239
|
+
request
|
240
|
+
),
|
241
|
+
))
|
242
|
+
}
|
243
|
+
|
244
|
+
pub fn parse_middleware(middleware: Value) -> Result<Middleware> {
|
245
|
+
let middleware_hash = RHash::from_value(middleware).ok_or(magnus::Error::new(
|
246
|
+
magnus::exception::standard_error(),
|
247
|
+
format!("Filter must be a hash. Got {:?}", middleware),
|
248
|
+
))?;
|
249
|
+
let middleware_type: String = middleware_hash
|
250
|
+
.get("type")
|
251
|
+
.ok_or(magnus::Error::new(
|
252
|
+
magnus::exception::standard_error(),
|
253
|
+
format!("Filter must have a :type key. Got {:?}", middleware_hash),
|
254
|
+
))?
|
255
|
+
.to_string();
|
256
|
+
let mw_type = middleware_type.clone();
|
257
|
+
|
258
|
+
let parameters: Value = middleware_hash.get("parameters").ok_or(magnus::Error::new(
|
259
|
+
magnus::exception::standard_error(),
|
260
|
+
format!(
|
261
|
+
"Filter must have a :parameters key. Got {:?}",
|
262
|
+
middleware_hash
|
263
|
+
),
|
264
|
+
))?;
|
265
|
+
|
266
|
+
let result = (move || -> Result<Middleware> {
|
267
|
+
match mw_type.as_str() {
|
268
|
+
"allow_list" => Ok(Middleware::AllowList(AllowList::from_value(parameters)?)),
|
269
|
+
"auth_basic" => Ok(Middleware::AuthBasic(AuthBasic::from_value(parameters)?)),
|
270
|
+
"auth_jwt" => Ok(Middleware::AuthJwt(Box::new(AuthJwt::from_value(
|
271
|
+
parameters,
|
272
|
+
)?))),
|
273
|
+
"auth_api_key" => Ok(Middleware::AuthAPIKey(AuthAPIKey::from_value(parameters)?)),
|
274
|
+
"cache_control" => Ok(Middleware::CacheControl(CacheControl::from_value(
|
275
|
+
parameters,
|
276
|
+
)?)),
|
277
|
+
"deny_list" => Ok(Middleware::DenyList(DenyList::from_value(parameters)?)),
|
278
|
+
"etag" => Ok(Middleware::ETag(ETag::from_value(parameters)?)),
|
279
|
+
"intrusion_protection" => Ok({
|
280
|
+
Middleware::IntrusionProtection(IntrusionProtection::from_value(parameters)?)
|
281
|
+
}),
|
282
|
+
"max_body" => Ok(Middleware::MaxBody(MaxBody::from_value(parameters)?)),
|
283
|
+
"rate_limit" => Ok(Middleware::RateLimit(RateLimit::from_value(parameters)?)),
|
284
|
+
"cors" => Ok(Middleware::Cors(Box::new(Cors::from_value(parameters)?))),
|
285
|
+
"request_headers" => Ok(Middleware::RequestHeaders(RequestHeaders::from_value(
|
286
|
+
parameters,
|
287
|
+
)?)),
|
288
|
+
"response_headers" => Ok(Middleware::ResponseHeaders(ResponseHeaders::from_value(
|
289
|
+
parameters,
|
290
|
+
)?)),
|
291
|
+
"static_assets" => Ok(Middleware::StaticAssets(StaticAssets::from_value(
|
292
|
+
parameters,
|
293
|
+
)?)),
|
294
|
+
"static_response" => Ok(Middleware::StaticResponse(StaticResponse::from_value(
|
295
|
+
parameters,
|
296
|
+
)?)),
|
297
|
+
"compression" => Ok(Middleware::Compression(Compression::from_value(
|
298
|
+
parameters,
|
299
|
+
)?)),
|
300
|
+
"log_requests" => Ok(Middleware::LogRequests(LogRequests::from_value(
|
301
|
+
parameters,
|
302
|
+
)?)),
|
303
|
+
"redirect" => Ok(Middleware::Redirect(Redirect::from_value(parameters)?)),
|
304
|
+
"app" => Ok(Middleware::RubyApp(RubyApp::from_value(parameters.into())?)),
|
305
|
+
"proxy" => Ok(Middleware::Proxy(Proxy::from_value(parameters)?)),
|
306
|
+
_ => Err(magnus::Error::new(
|
307
|
+
magnus::exception::standard_error(),
|
308
|
+
format!("Unknown filter type: {}", mw_type),
|
309
|
+
)),
|
310
|
+
}
|
311
|
+
})();
|
312
|
+
|
313
|
+
match result {
|
314
|
+
Ok(result) => Ok(result),
|
315
|
+
Err(err) => Err(magnus::Error::new(
|
316
|
+
magnus::exception::standard_error(),
|
317
|
+
format!(
|
318
|
+
"Failed to instantiate middleware of type {}, due to {}",
|
319
|
+
middleware_type, err
|
320
|
+
),
|
321
|
+
)),
|
322
|
+
}
|
323
|
+
}
|
324
|
+
|
325
|
+
pub async fn initialize_layers(&self) -> Result<()> {
|
326
|
+
for stack in self.stacks.values() {
|
327
|
+
for middleware in &stack.layers {
|
328
|
+
middleware.initialize().await?;
|
329
|
+
}
|
330
|
+
}
|
331
|
+
Ok(())
|
332
|
+
}
|
333
|
+
}
|
334
|
+
|
335
|
+
fn extract_optional_match_array(route_hash: RHash, arg: &str) -> Result<Option<Vec<StringMatch>>> {
|
336
|
+
let rarray = route_hash.aref::<_, Option<RArray>>(arg)?;
|
337
|
+
if let Some(array) = rarray {
|
338
|
+
Ok(Some(
|
339
|
+
array
|
340
|
+
.into_iter()
|
341
|
+
.map(StringMatch::from_value)
|
342
|
+
.collect::<Result<Vec<StringMatch>>>()?,
|
343
|
+
))
|
344
|
+
} else {
|
345
|
+
Ok(None)
|
346
|
+
}
|
347
|
+
}
|
@@ -1,5 +1,12 @@
|
|
1
|
-
pub mod
|
2
|
-
pub mod
|
3
|
-
pub mod
|
4
|
-
pub mod
|
5
|
-
pub mod
|
1
|
+
pub mod binds;
|
2
|
+
pub mod byte_frame;
|
3
|
+
pub mod http_message_types;
|
4
|
+
pub mod io_stream;
|
5
|
+
pub mod lifecycle_event;
|
6
|
+
pub mod middleware_stack;
|
7
|
+
pub mod process_worker;
|
8
|
+
pub mod request_job;
|
9
|
+
pub mod serve_strategy;
|
10
|
+
pub mod signal;
|
11
|
+
pub mod size_limited_incoming;
|
12
|
+
pub mod thread_worker;
|
@@ -0,0 +1,247 @@
|
|
1
|
+
use super::serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode};
|
2
|
+
use core_affinity::CoreId;
|
3
|
+
use itsi_error::{ItsiError, Result};
|
4
|
+
use itsi_rb_helpers::{call_with_gvl, call_without_gvl, create_ruby_thread, fork};
|
5
|
+
use itsi_tracing::error;
|
6
|
+
use nix::{
|
7
|
+
errno::Errno,
|
8
|
+
sys::{
|
9
|
+
signal::{
|
10
|
+
kill,
|
11
|
+
Signal::{SIGKILL, SIGTERM, SIGUSR2},
|
12
|
+
},
|
13
|
+
wait::{waitpid, WaitPidFlag, WaitStatus},
|
14
|
+
},
|
15
|
+
unistd::{setpgid, Pid},
|
16
|
+
};
|
17
|
+
use parking_lot::Mutex;
|
18
|
+
use std::{
|
19
|
+
process::{self, exit},
|
20
|
+
sync::{Arc, LazyLock},
|
21
|
+
time::{Duration, Instant},
|
22
|
+
};
|
23
|
+
use sysinfo::System;
|
24
|
+
|
25
|
+
use tokio::{sync::watch, time::sleep};
|
26
|
+
use tracing::{info, instrument, warn};
|
27
|
+
|
28
|
+
#[derive(Clone, Debug)]
|
29
|
+
pub struct ProcessWorker {
|
30
|
+
pub worker_id: usize,
|
31
|
+
pub child_pid: Arc<Mutex<Option<Pid>>>,
|
32
|
+
pub started_at: Instant,
|
33
|
+
}
|
34
|
+
|
35
|
+
impl Default for ProcessWorker {
|
36
|
+
fn default() -> Self {
|
37
|
+
Self {
|
38
|
+
worker_id: 0,
|
39
|
+
child_pid: Arc::new(Mutex::new(None)),
|
40
|
+
started_at: Instant::now(),
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
static CORE_IDS: LazyLock<Vec<CoreId>> = LazyLock::new(|| core_affinity::get_core_ids().unwrap());
|
46
|
+
|
47
|
+
impl ProcessWorker {
|
48
|
+
#[instrument(skip(self, cluster_template), fields(self.worker_id = %self.worker_id))]
|
49
|
+
pub(crate) fn boot(&self, cluster_template: Arc<ClusterMode>) -> Result<()> {
|
50
|
+
let child_pid = *self.child_pid.lock();
|
51
|
+
if let Some(pid) = child_pid {
|
52
|
+
if self.is_alive() {
|
53
|
+
if let Err(e) = kill(pid, SIGTERM) {
|
54
|
+
info!("Failed to send SIGTERM to process {}: {}", pid, e);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
*self.child_pid.lock() = None;
|
58
|
+
}
|
59
|
+
|
60
|
+
match call_with_gvl(|_ruby| {
|
61
|
+
fork(
|
62
|
+
cluster_template
|
63
|
+
.server_config
|
64
|
+
.server_params
|
65
|
+
.read()
|
66
|
+
.hooks
|
67
|
+
.get("after_fork")
|
68
|
+
.cloned(),
|
69
|
+
)
|
70
|
+
}) {
|
71
|
+
Some(pid) => {
|
72
|
+
*self.child_pid.lock() = Some(Pid::from_raw(pid));
|
73
|
+
}
|
74
|
+
None => {
|
75
|
+
if let Err(e) = setpgid(
|
76
|
+
Pid::from_raw(process::id() as i32),
|
77
|
+
Pid::from_raw(process::id() as i32),
|
78
|
+
) {
|
79
|
+
error!("Failed to set process group ID: {}", e);
|
80
|
+
}
|
81
|
+
match SingleMode::new(cluster_template.server_config.clone()) {
|
82
|
+
Ok(single_mode) => {
|
83
|
+
if cluster_template
|
84
|
+
.server_config
|
85
|
+
.server_params
|
86
|
+
.read()
|
87
|
+
.pin_worker_cores
|
88
|
+
{
|
89
|
+
core_affinity::set_for_current(
|
90
|
+
CORE_IDS[self.worker_id % CORE_IDS.len()],
|
91
|
+
);
|
92
|
+
}
|
93
|
+
Arc::new(single_mode).run().ok();
|
94
|
+
}
|
95
|
+
Err(e) => {
|
96
|
+
error!("Failed to boot into worker mode: {}", e);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
exit(0)
|
100
|
+
}
|
101
|
+
}
|
102
|
+
Ok(())
|
103
|
+
}
|
104
|
+
|
105
|
+
pub fn pid(&self) -> i32 {
|
106
|
+
if let Some(pid) = *self.child_pid.lock() {
|
107
|
+
return pid.as_raw();
|
108
|
+
}
|
109
|
+
0
|
110
|
+
}
|
111
|
+
|
112
|
+
pub(crate) fn memory_usage(&self) -> Option<u64> {
|
113
|
+
if let Some(pid) = *self.child_pid.lock() {
|
114
|
+
let s = System::new_all();
|
115
|
+
if let Some(process) = s.process(sysinfo::Pid::from(pid.as_raw() as usize)) {
|
116
|
+
return Some(process.memory());
|
117
|
+
}
|
118
|
+
}
|
119
|
+
None
|
120
|
+
}
|
121
|
+
|
122
|
+
pub(crate) async fn reboot(&self, cluster_template: Arc<ClusterMode>) -> Result<bool> {
|
123
|
+
self.graceful_shutdown(cluster_template.clone()).await;
|
124
|
+
let self_clone = self.clone();
|
125
|
+
let (booted_sender, mut booted_receiver) = watch::channel(false);
|
126
|
+
create_ruby_thread(move || {
|
127
|
+
call_without_gvl(move || {
|
128
|
+
if self_clone.boot(cluster_template).is_ok() {
|
129
|
+
booted_sender.send(true).ok()
|
130
|
+
} else {
|
131
|
+
booted_sender.send(false).ok()
|
132
|
+
};
|
133
|
+
})
|
134
|
+
});
|
135
|
+
|
136
|
+
booted_receiver
|
137
|
+
.changed()
|
138
|
+
.await
|
139
|
+
.map_err(|_| ItsiError::InternalServerError("Failed to boot worker".to_owned()))?;
|
140
|
+
|
141
|
+
let guard = booted_receiver.borrow();
|
142
|
+
let result = guard.to_owned();
|
143
|
+
// Not very robust, we should check to see if the worker is actually listening before considering this successful.
|
144
|
+
sleep(Duration::from_secs(1)).await;
|
145
|
+
Ok(result)
|
146
|
+
}
|
147
|
+
|
148
|
+
pub(crate) async fn graceful_shutdown(&self, cluster_template: Arc<ClusterMode>) {
|
149
|
+
let self_clone = self.clone();
|
150
|
+
self_clone.request_shutdown();
|
151
|
+
let force_kill_time = Instant::now()
|
152
|
+
+ Duration::from_secs_f64(
|
153
|
+
cluster_template
|
154
|
+
.server_config
|
155
|
+
.server_params
|
156
|
+
.read()
|
157
|
+
.shutdown_timeout,
|
158
|
+
);
|
159
|
+
while self_clone.is_alive() && force_kill_time > Instant::now() {
|
160
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
161
|
+
}
|
162
|
+
if self_clone.is_alive() {
|
163
|
+
self_clone.force_kill();
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
pub(crate) fn boot_if_dead(&self, cluster_template: Arc<ClusterMode>) -> bool {
|
168
|
+
if !self.is_alive() {
|
169
|
+
if self.just_started() {
|
170
|
+
error!(
|
171
|
+
"Worker in crash loop {:?}. Refusing to restart",
|
172
|
+
self.child_pid.lock()
|
173
|
+
);
|
174
|
+
return false;
|
175
|
+
} else {
|
176
|
+
let self_clone = self.clone();
|
177
|
+
create_ruby_thread(move || {
|
178
|
+
call_without_gvl(move || {
|
179
|
+
self_clone.boot(cluster_template).ok();
|
180
|
+
})
|
181
|
+
});
|
182
|
+
}
|
183
|
+
}
|
184
|
+
true
|
185
|
+
}
|
186
|
+
|
187
|
+
pub(crate) fn request_shutdown(&self) {
|
188
|
+
let child_pid = *self.child_pid.lock();
|
189
|
+
if let Some(pid) = child_pid {
|
190
|
+
if self.is_alive() {
|
191
|
+
if let Err(e) = kill(pid, SIGTERM) {
|
192
|
+
error!("Failed to send SIGTERM to process {}: {}", pid, e);
|
193
|
+
}
|
194
|
+
} else {
|
195
|
+
error!("Trying to shutdown a dead process");
|
196
|
+
}
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
pub(crate) fn force_kill(&self) {
|
201
|
+
let child_pid = *self.child_pid.lock();
|
202
|
+
if let Some(pid) = child_pid {
|
203
|
+
if self.is_alive() {
|
204
|
+
info!("Worker still alive, sending SIGKILL {}", pid);
|
205
|
+
if let Err(e) = kill(pid, SIGKILL) {
|
206
|
+
error!("Failed to force kill process {}: {}", pid, e);
|
207
|
+
}
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
pub fn print_info(&self) -> Result<()> {
|
213
|
+
let child_pid = *self.child_pid.lock();
|
214
|
+
if let Some(pid) = child_pid {
|
215
|
+
println!("Worker {:?}, PID: {:?}", self.worker_id, pid);
|
216
|
+
if let Err(e) = kill(pid, SIGUSR2) {
|
217
|
+
error!("Failed to send SIGUSR2 to process {}: {}", pid, e);
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
Ok(())
|
222
|
+
}
|
223
|
+
|
224
|
+
pub(crate) fn just_started(&self) -> bool {
|
225
|
+
let now = Instant::now();
|
226
|
+
now.duration_since(self.started_at).as_millis() < 2000
|
227
|
+
}
|
228
|
+
|
229
|
+
pub(crate) fn is_alive(&self) -> bool {
|
230
|
+
let child_pid = *self.child_pid.lock();
|
231
|
+
if let Some(pid) = child_pid {
|
232
|
+
match waitpid(pid, Some(WaitPidFlag::WNOHANG)) {
|
233
|
+
Ok(WaitStatus::Exited(_, _)) | Ok(WaitStatus::Signaled(_, _, _)) => {
|
234
|
+
return false;
|
235
|
+
}
|
236
|
+
Ok(WaitStatus::StillAlive) | Ok(_) => {}
|
237
|
+
Err(_) => return false,
|
238
|
+
}
|
239
|
+
match kill(pid, None) {
|
240
|
+
Ok(_) => true,
|
241
|
+
Err(errno) => !matches!(errno, Errno::ESRCH),
|
242
|
+
}
|
243
|
+
} else {
|
244
|
+
false
|
245
|
+
}
|
246
|
+
}
|
247
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
use crate::ruby_types::{itsi_grpc_call::ItsiGrpcCall, itsi_http_request::ItsiHttpRequest};
|
2
|
+
use itsi_rb_helpers::HeapValue;
|
3
|
+
use magnus::block::Proc;
|
4
|
+
use std::sync::Arc;
|
5
|
+
|
6
|
+
#[derive(Debug)]
|
7
|
+
pub enum RequestJob {
|
8
|
+
ProcessHttpRequest(ItsiHttpRequest, Arc<HeapValue<Proc>>),
|
9
|
+
ProcessGrpcRequest(ItsiGrpcCall, Arc<HeapValue<Proc>>),
|
10
|
+
Shutdown,
|
11
|
+
}
|