itsi-scheduler 0.1.5 → 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.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +120 -52
  3. data/README.md +57 -24
  4. data/Rakefile +0 -4
  5. data/ext/itsi_acme/Cargo.toml +86 -0
  6. data/ext/itsi_acme/examples/high_level.rs +63 -0
  7. data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
  8. data/ext/itsi_acme/examples/low_level.rs +87 -0
  9. data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
  10. data/ext/itsi_acme/src/acceptor.rs +81 -0
  11. data/ext/itsi_acme/src/acme.rs +354 -0
  12. data/ext/itsi_acme/src/axum.rs +86 -0
  13. data/ext/itsi_acme/src/cache.rs +39 -0
  14. data/ext/itsi_acme/src/caches/boxed.rs +80 -0
  15. data/ext/itsi_acme/src/caches/composite.rs +69 -0
  16. data/ext/itsi_acme/src/caches/dir.rs +106 -0
  17. data/ext/itsi_acme/src/caches/mod.rs +11 -0
  18. data/ext/itsi_acme/src/caches/no.rs +78 -0
  19. data/ext/itsi_acme/src/caches/test.rs +136 -0
  20. data/ext/itsi_acme/src/config.rs +172 -0
  21. data/ext/itsi_acme/src/https_helper.rs +69 -0
  22. data/ext/itsi_acme/src/incoming.rs +142 -0
  23. data/ext/itsi_acme/src/jose.rs +161 -0
  24. data/ext/itsi_acme/src/lib.rs +142 -0
  25. data/ext/itsi_acme/src/resolver.rs +59 -0
  26. data/ext/itsi_acme/src/state.rs +424 -0
  27. data/ext/itsi_error/Cargo.toml +1 -0
  28. data/ext/itsi_error/src/lib.rs +106 -7
  29. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  30. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  31. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  32. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  33. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  34. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  35. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  36. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  37. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  38. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  39. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  40. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  41. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  42. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  43. data/ext/itsi_rb_helpers/Cargo.toml +1 -0
  44. data/ext/itsi_rb_helpers/src/heap_value.rs +18 -0
  45. data/ext/itsi_rb_helpers/src/lib.rs +63 -12
  46. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  47. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  48. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  49. 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
  50. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  51. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  52. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  53. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  54. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  55. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  56. data/ext/itsi_scheduler/Cargo.toml +1 -1
  57. data/ext/itsi_scheduler/src/itsi_scheduler.rs +9 -3
  58. data/ext/itsi_scheduler/src/lib.rs +1 -0
  59. data/ext/itsi_server/Cargo.lock +2956 -0
  60. data/ext/itsi_server/Cargo.toml +73 -29
  61. data/ext/itsi_server/src/default_responses/mod.rs +11 -0
  62. data/ext/itsi_server/src/env.rs +43 -0
  63. data/ext/itsi_server/src/lib.rs +114 -75
  64. data/ext/itsi_server/src/prelude.rs +2 -0
  65. data/ext/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
  66. data/ext/itsi_server/src/{body_proxy/itsi_body_proxy.rs → ruby_types/itsi_body_proxy/mod.rs} +29 -8
  67. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  68. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
  69. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +362 -0
  70. data/ext/itsi_server/src/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +84 -40
  71. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +233 -0
  72. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +565 -0
  73. data/ext/itsi_server/src/ruby_types/itsi_server.rs +86 -0
  74. data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
  75. data/ext/itsi_server/src/server/{bind.rs → binds/bind.rs} +59 -24
  76. data/ext/itsi_server/src/server/binds/listener.rs +444 -0
  77. data/ext/itsi_server/src/server/binds/mod.rs +4 -0
  78. data/ext/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +57 -19
  79. data/ext/itsi_server/src/server/{tls.rs → binds/tls.rs} +120 -31
  80. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  81. data/ext/itsi_server/src/server/http_message_types.rs +97 -0
  82. data/ext/itsi_server/src/server/io_stream.rs +2 -1
  83. data/ext/itsi_server/src/server/lifecycle_event.rs +3 -0
  84. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +170 -0
  85. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +63 -0
  86. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +94 -0
  87. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +94 -0
  88. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +343 -0
  89. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +151 -0
  90. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +316 -0
  91. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +301 -0
  92. data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +193 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +64 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +192 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +171 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +198 -0
  97. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
  98. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +209 -0
  99. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  100. data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  101. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +116 -0
  102. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +411 -0
  103. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +142 -0
  104. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +55 -0
  105. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +54 -0
  106. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +51 -0
  107. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
  108. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +187 -0
  109. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  110. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +173 -0
  111. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +31 -0
  112. data/ext/itsi_server/src/server/middleware_stack/mod.rs +381 -0
  113. data/ext/itsi_server/src/server/mod.rs +7 -5
  114. data/ext/itsi_server/src/server/process_worker.rs +65 -14
  115. data/ext/itsi_server/src/server/redirect_type.rs +26 -0
  116. data/ext/itsi_server/src/server/request_job.rs +11 -0
  117. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +150 -50
  118. data/ext/itsi_server/src/server/serve_strategy/mod.rs +9 -6
  119. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +399 -165
  120. data/ext/itsi_server/src/server/signal.rs +33 -26
  121. data/ext/itsi_server/src/server/size_limited_incoming.rs +107 -0
  122. data/ext/itsi_server/src/server/thread_worker.rs +218 -107
  123. data/ext/itsi_server/src/services/cache_store.rs +74 -0
  124. data/ext/itsi_server/src/services/itsi_http_service.rs +257 -0
  125. data/ext/itsi_server/src/services/mime_types.rs +1416 -0
  126. data/ext/itsi_server/src/services/mod.rs +6 -0
  127. data/ext/itsi_server/src/services/password_hasher.rs +83 -0
  128. data/ext/itsi_server/src/services/rate_limiter.rs +580 -0
  129. data/ext/itsi_server/src/services/static_file_server.rs +1340 -0
  130. data/ext/itsi_tracing/Cargo.toml +1 -0
  131. data/ext/itsi_tracing/src/lib.rs +362 -33
  132. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  133. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  134. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  135. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  136. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  137. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  138. data/itsi-scheduler-100.png +0 -0
  139. data/lib/itsi/scheduler/version.rb +1 -1
  140. data/lib/itsi/scheduler.rb +11 -6
  141. metadata +117 -24
  142. data/CHANGELOG.md +0 -5
  143. data/CODE_OF_CONDUCT.md +0 -132
  144. data/LICENSE.txt +0 -21
  145. data/ext/itsi_error/src/from.rs +0 -71
  146. data/ext/itsi_server/extconf.rb +0 -6
  147. data/ext/itsi_server/src/body_proxy/mod.rs +0 -2
  148. data/ext/itsi_server/src/request/itsi_request.rs +0 -277
  149. data/ext/itsi_server/src/request/mod.rs +0 -1
  150. data/ext/itsi_server/src/response/mod.rs +0 -1
  151. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
  152. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
  153. data/ext/itsi_server/src/server/itsi_server.rs +0 -244
  154. data/ext/itsi_server/src/server/listener.rs +0 -327
  155. /data/ext/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
@@ -0,0 +1,381 @@
1
+ mod middleware;
2
+ mod middlewares;
3
+ use http::header::{ACCEPT, CONTENT_TYPE, HOST};
4
+ use itsi_rb_helpers::HeapVal;
5
+ use magnus::{
6
+ error::Result, rb_sys::AsRawValue, value::ReprValue, RArray, RHash, Ruby, TryConvert, Value,
7
+ };
8
+ pub use middleware::Middleware;
9
+ pub use middlewares::*;
10
+ use regex::{Regex, RegexSet};
11
+ use std::{
12
+ collections::{hash_map::Entry::Vacant, HashMap},
13
+ sync::Arc,
14
+ };
15
+ use tracing::debug;
16
+
17
+ use super::http_message_types::HttpRequest;
18
+
19
+ #[derive(Debug)]
20
+ pub struct MiddlewareSet {
21
+ pub route_set: RegexSet,
22
+ pub patterns: Vec<Arc<Regex>>,
23
+ pub stacks: HashMap<usize, MiddlewareStack>,
24
+ unique_middlewares: HashMap<u64, Middleware>,
25
+ }
26
+
27
+ #[derive(Debug)]
28
+ pub struct MiddlewareStack {
29
+ layers: Vec<Middleware>,
30
+ methods: Option<Vec<StringMatch>>,
31
+ protocols: Option<Vec<StringMatch>>,
32
+ hosts: Option<Vec<StringMatch>>,
33
+ extensions: Option<Vec<StringMatch>>,
34
+ ports: Option<Vec<StringMatch>>,
35
+ content_types: Option<Vec<StringMatch>>,
36
+ accepts: Option<Vec<StringMatch>>,
37
+ }
38
+
39
+ #[derive(Debug)]
40
+ enum StringMatch {
41
+ Exact(String),
42
+ Wildcard(Regex),
43
+ }
44
+
45
+ impl StringMatch {
46
+ fn from_value(value: Value) -> Result<Self> {
47
+ let ruby = Ruby::get().unwrap();
48
+ if value.is_kind_of(ruby.class_regexp()) {
49
+ let src_str = value.funcall::<_, _, String>("source", ())?;
50
+ let regex = Regex::new(&src_str).map_err(|e| {
51
+ magnus::Error::new(
52
+ magnus::exception::standard_error(),
53
+ format!("Invalid regexp: {}", e),
54
+ )
55
+ })?;
56
+ Ok(StringMatch::Wildcard(regex))
57
+ } else {
58
+ Ok(StringMatch::Exact(value.to_string()))
59
+ }
60
+ }
61
+
62
+ fn matches(&self, value: &str) -> bool {
63
+ match self {
64
+ StringMatch::Exact(s) => s.eq_ignore_ascii_case(value),
65
+ StringMatch::Wildcard(re) => re.is_match(value),
66
+ }
67
+ }
68
+ }
69
+
70
+ impl MiddlewareStack {
71
+ pub fn matches(&self, request: &HttpRequest) -> bool {
72
+ if let Some(methods) = &self.methods {
73
+ let method = request.method().as_str();
74
+ if !methods.iter().any(|m| m.matches(method)) {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ if let (Some(protocols), Some(protocol)) = (&self.protocols, request.uri().scheme()) {
80
+ if !protocols.iter().any(|p| p.matches(protocol.as_str())) {
81
+ return false;
82
+ }
83
+ }
84
+
85
+ if let (Some(hosts), Some(host)) = (&self.hosts, request.headers().get(HOST)) {
86
+ if let Ok(host) = host.to_str() {
87
+ if !hosts.iter().any(|d| d.matches(host)) {
88
+ return false;
89
+ }
90
+ }
91
+ }
92
+
93
+ if let (Some(ports), Some(port)) = (&self.ports, request.uri().port()) {
94
+ if !ports.iter().any(|d| d.matches(port.as_str())) {
95
+ return false;
96
+ }
97
+ }
98
+
99
+ if let Some(extensions) = &self.extensions {
100
+ let path = request.uri().path();
101
+ let segment = path.split('/').next_back().unwrap_or("");
102
+ let extension = segment.split('.').next_back().unwrap_or("");
103
+ let extension = if segment != extension { extension } else { "" };
104
+ if !extensions.iter().any(|e| e.matches(extension)) {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ if let Some(content_types) = &self.content_types {
110
+ if let Some(content_type) = request.headers().get(CONTENT_TYPE) {
111
+ if !content_types
112
+ .iter()
113
+ .any(|ct| ct.matches(content_type.to_str().unwrap_or("")))
114
+ {
115
+ return false;
116
+ }
117
+ }
118
+ }
119
+
120
+ if let Some(accepts) = &self.accepts {
121
+ if let Some(accept) = request.headers().get(ACCEPT) {
122
+ if !accepts
123
+ .iter()
124
+ .any(|a| a.matches(accept.to_str().unwrap_or("")))
125
+ {
126
+ return false;
127
+ }
128
+ }
129
+ }
130
+
131
+ true
132
+ }
133
+ }
134
+
135
+ impl MiddlewareSet {
136
+ pub fn new(routes_raw: Option<HeapVal>) -> Result<Self> {
137
+ let mut unique_middlewares = HashMap::new();
138
+ clear_value_cache();
139
+
140
+ if let Some(routes_raw) = routes_raw {
141
+ let mut stacks = HashMap::new();
142
+ let mut routes = vec![];
143
+ for (index, route) in RArray::from_value(*routes_raw)
144
+ .ok_or(magnus::Error::new(
145
+ magnus::exception::standard_error(),
146
+ format!("Routes must be an array. Got {:?}", routes_raw),
147
+ ))?
148
+ .into_iter()
149
+ .enumerate()
150
+ {
151
+ let route_hash: RHash = RHash::try_convert(route)?;
152
+ let route_raw = route_hash
153
+ .get("route")
154
+ .ok_or(magnus::Error::new(
155
+ magnus::exception::standard_error(),
156
+ "Route is missing :route key",
157
+ ))?
158
+ .funcall::<_, _, String>("source", ())?;
159
+
160
+ let middleware =
161
+ RHash::from_value(route_hash.get("middleware").ok_or(magnus::Error::new(
162
+ magnus::exception::standard_error(),
163
+ "Route is missing middleware key",
164
+ ))?)
165
+ .ok_or(magnus::Error::new(
166
+ magnus::exception::standard_error(),
167
+ format!("middleware must be a hash. Got {:?}", routes_raw),
168
+ ))?;
169
+
170
+ let ruby = Ruby::get().unwrap();
171
+ let mut layers = middleware
172
+ .enumeratorize("each", ())
173
+ .flat_map(|pair| {
174
+ let pair = RArray::from_value(pair.unwrap()).unwrap();
175
+ let middleware_type: String = pair.entry(0).unwrap();
176
+ let value: Value = pair.entry(1).unwrap();
177
+ let middleware_values: Vec<Value> = if value.is_kind_of(ruby.class_array())
178
+ {
179
+ RArray::from_value(value)
180
+ .ok_or_else(|| {
181
+ magnus::Error::new(
182
+ magnus::exception::type_error(),
183
+ "Expected array",
184
+ )
185
+ })
186
+ .unwrap()
187
+ .into_iter()
188
+ .collect()
189
+ } else {
190
+ vec![value]
191
+ };
192
+ middleware_values
193
+ .into_iter()
194
+ .map(|value| {
195
+ let middleware =
196
+ if let Vacant(e) = unique_middlewares.entry(value.as_raw()) {
197
+ let middleware = MiddlewareSet::parse_middleware(
198
+ middleware_type.clone(),
199
+ value,
200
+ );
201
+ if let Ok(middleware) = middleware.as_ref() {
202
+ e.insert(middleware.clone());
203
+ };
204
+ middleware
205
+ } else {
206
+ Ok(unique_middlewares.get(&value.as_raw()).unwrap().clone())
207
+ };
208
+ middleware
209
+ })
210
+ .collect::<Vec<Result<Middleware>>>()
211
+ })
212
+ .collect::<Result<Vec<_>>>()?;
213
+ routes.push(route_raw);
214
+ layers.sort();
215
+
216
+ debug!("Middleware at index: {} has layers: {:?}", index, layers);
217
+
218
+ stacks.insert(
219
+ index,
220
+ MiddlewareStack {
221
+ layers,
222
+ methods: extract_optional_match_array(route_hash, "methods")?,
223
+ protocols: extract_optional_match_array(route_hash, "protocols")?,
224
+ hosts: extract_optional_match_array(route_hash, "hosts")?,
225
+ extensions: extract_optional_match_array(route_hash, "extensions")?,
226
+ ports: extract_optional_match_array(route_hash, "ports")?,
227
+ content_types: extract_optional_match_array(route_hash, "content_types")?,
228
+ accepts: extract_optional_match_array(route_hash, "accepts")?,
229
+ },
230
+ );
231
+ }
232
+ Ok(Self {
233
+ route_set: RegexSet::new(&routes).map_err(|e| {
234
+ magnus::Error::new(
235
+ magnus::exception::standard_error(),
236
+ format!("Failed to create route set: {}", e),
237
+ )
238
+ })?,
239
+ unique_middlewares,
240
+ patterns: routes
241
+ .into_iter()
242
+ .map(|r| Regex::new(&r))
243
+ .collect::<std::result::Result<Vec<Regex>, regex::Error>>()
244
+ .map_err(|e| {
245
+ magnus::Error::new(
246
+ magnus::exception::standard_error(),
247
+ format!("Failed to create route set: {}", e),
248
+ )
249
+ })?
250
+ .into_iter()
251
+ .map(Arc::new)
252
+ .collect(),
253
+ stacks,
254
+ })
255
+ } else {
256
+ Err(magnus::Error::new(
257
+ magnus::exception::standard_error(),
258
+ "Failed to create middleware stack",
259
+ ))
260
+ }
261
+ }
262
+
263
+ pub fn stack_for(
264
+ &self,
265
+ request: &HttpRequest,
266
+ ) -> Result<(&Vec<Middleware>, Option<Arc<Regex>>)> {
267
+ let binding = self.route_set.matches(request.uri().path());
268
+ let matches = binding.iter();
269
+
270
+ debug!(
271
+ "Matching request URI {:?} against self.route_set: {:?}",
272
+ request.uri().path(),
273
+ self.route_set
274
+ );
275
+ for index in matches {
276
+ let matching_pattern = self.patterns.get(index).cloned();
277
+ if let Some(stack) = self.stacks.get(&index) {
278
+ if stack.matches(request) {
279
+ return Ok((&stack.layers, matching_pattern));
280
+ }
281
+ }
282
+ }
283
+ debug!(
284
+ "Failed to match request URI {:?} to self.route_set: {:?}",
285
+ request.uri().path(),
286
+ self.route_set
287
+ );
288
+ Err(magnus::Error::new(
289
+ magnus::exception::standard_error(),
290
+ format!(
291
+ "No matching middleware stack found for request: {:?}",
292
+ request
293
+ ),
294
+ ))
295
+ }
296
+
297
+ pub fn parse_middleware(middleware_type: String, parameters: Value) -> Result<Middleware> {
298
+ let mw_type = middleware_type.clone();
299
+
300
+ debug!(target: "itsi_server::middleware_stack", "Parsing middleware: {} from {}", mw_type, parameters);
301
+ let result = (move || -> Result<Middleware> {
302
+ match mw_type.as_str() {
303
+ "allow_list" => Ok(Middleware::AllowList(AllowList::from_value(parameters)?)),
304
+ "auth_basic" => Ok(Middleware::AuthBasic(AuthBasic::from_value(parameters)?)),
305
+ "auth_jwt" => Ok(Middleware::AuthJwt(AuthJwt::from_value(parameters)?)),
306
+ "auth_api_key" => Ok(Middleware::AuthAPIKey(AuthAPIKey::from_value(parameters)?)),
307
+ "cache_control" => Ok(Middleware::CacheControl(CacheControl::from_value(
308
+ parameters,
309
+ )?)),
310
+ "deny_list" => Ok(Middleware::DenyList(DenyList::from_value(parameters)?)),
311
+ "etag" => Ok(Middleware::ETag(ETag::from_value(parameters)?)),
312
+ "csp" => Ok(Middleware::Csp(Csp::from_value(parameters)?)),
313
+ "intrusion_protection" => Ok({
314
+ Middleware::IntrusionProtection(IntrusionProtection::from_value(parameters)?)
315
+ }),
316
+ "max_body" => Ok(Middleware::MaxBody(MaxBody::from_value(parameters)?)),
317
+ "rate_limit" => Ok(Middleware::RateLimit(RateLimit::from_value(parameters)?)),
318
+ "cors" => Ok(Middleware::Cors(Cors::from_value(parameters)?)),
319
+ "request_headers" => Ok(Middleware::RequestHeaders(RequestHeaders::from_value(
320
+ parameters,
321
+ )?)),
322
+ "response_headers" => Ok(Middleware::ResponseHeaders(ResponseHeaders::from_value(
323
+ parameters,
324
+ )?)),
325
+ "static_assets" => Ok(Middleware::StaticAssets(StaticAssets::from_value(
326
+ parameters,
327
+ )?)),
328
+ "static_response" => Ok(Middleware::StaticResponse(StaticResponse::from_value(
329
+ parameters,
330
+ )?)),
331
+ "compress" => Ok(Middleware::Compression(Compression::from_value(
332
+ parameters,
333
+ )?)),
334
+ "log_requests" => Ok(Middleware::LogRequests(LogRequests::from_value(
335
+ parameters,
336
+ )?)),
337
+ "redirect" => Ok(Middleware::Redirect(Redirect::from_value(parameters)?)),
338
+ "app" => Ok(Middleware::RubyApp(RubyApp::from_value(parameters.into())?)),
339
+ "proxy" => Ok(Middleware::Proxy(Proxy::from_value(parameters)?)),
340
+ _ => Err(magnus::Error::new(
341
+ magnus::exception::standard_error(),
342
+ format!("Unknown filter type: {}", mw_type),
343
+ )),
344
+ }
345
+ })();
346
+
347
+ debug!(target: "itsi_server::middleware_stack", "Stack layer init result {:?}", result);
348
+
349
+ match result {
350
+ Ok(result) => Ok(result),
351
+ Err(err) => Err(magnus::Error::new(
352
+ magnus::exception::standard_error(),
353
+ format!(
354
+ "Failed to instantiate middleware of type {}, due to {}",
355
+ middleware_type, err
356
+ ),
357
+ )),
358
+ }
359
+ }
360
+
361
+ pub async fn initialize_layers(&self) -> Result<()> {
362
+ for middleware in self.unique_middlewares.values() {
363
+ middleware.initialize().await?;
364
+ }
365
+ Ok(())
366
+ }
367
+ }
368
+
369
+ fn extract_optional_match_array(route_hash: RHash, arg: &str) -> Result<Option<Vec<StringMatch>>> {
370
+ let rarray = route_hash.aref::<_, Option<RArray>>(arg)?;
371
+ if let Some(array) = rarray {
372
+ Ok(Some(
373
+ array
374
+ .into_iter()
375
+ .map(StringMatch::from_value)
376
+ .collect::<Result<Vec<StringMatch>>>()?,
377
+ ))
378
+ } else {
379
+ Ok(None)
380
+ }
381
+ }
@@ -1,11 +1,13 @@
1
- pub mod bind;
2
- pub mod bind_protocol;
1
+ pub mod binds;
2
+ pub mod byte_frame;
3
+ pub mod http_message_types;
3
4
  pub mod io_stream;
4
- pub mod itsi_server;
5
5
  pub mod lifecycle_event;
6
- pub mod listener;
6
+ pub mod middleware_stack;
7
7
  pub mod process_worker;
8
+ pub mod redirect_type;
9
+ pub mod request_job;
8
10
  pub mod serve_strategy;
9
11
  pub mod signal;
12
+ pub mod size_limited_incoming;
10
13
  pub mod thread_worker;
11
- pub mod tls;
@@ -1,4 +1,5 @@
1
1
  use super::serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode};
2
+ use core_affinity::CoreId;
2
3
  use itsi_error::{ItsiError, Result};
3
4
  use itsi_rb_helpers::{call_with_gvl, call_without_gvl, create_ruby_thread, fork};
4
5
  use itsi_tracing::error;
@@ -7,7 +8,7 @@ use nix::{
7
8
  sys::{
8
9
  signal::{
9
10
  kill,
10
- Signal::{SIGKILL, SIGTERM},
11
+ Signal::{SIGKILL, SIGTERM, SIGUSR2},
11
12
  },
12
13
  wait::{waitpid, WaitPidFlag, WaitStatus},
13
14
  },
@@ -16,7 +17,7 @@ use nix::{
16
17
  use parking_lot::Mutex;
17
18
  use std::{
18
19
  process::{self, exit},
19
- sync::Arc,
20
+ sync::{Arc, LazyLock},
20
21
  time::{Duration, Instant},
21
22
  };
22
23
  use sysinfo::System;
@@ -41,6 +42,8 @@ impl Default for ProcessWorker {
41
42
  }
42
43
  }
43
44
 
45
+ static CORE_IDS: LazyLock<Vec<CoreId>> = LazyLock::new(|| core_affinity::get_core_ids().unwrap());
46
+
44
47
  impl ProcessWorker {
45
48
  #[instrument(skip(self, cluster_template), fields(self.worker_id = %self.worker_id))]
46
49
  pub(crate) fn boot(&self, cluster_template: Arc<ClusterMode>) -> Result<()> {
@@ -54,7 +57,17 @@ impl ProcessWorker {
54
57
  *self.child_pid.lock() = None;
55
58
  }
56
59
 
57
- match call_with_gvl(|_ruby| fork(cluster_template.server.after_fork.lock().clone())) {
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
+ }) {
58
71
  Some(pid) => {
59
72
  *self.child_pid.lock() = Some(Pid::from_raw(pid));
60
73
  }
@@ -65,12 +78,18 @@ impl ProcessWorker {
65
78
  ) {
66
79
  error!("Failed to set process group ID: {}", e);
67
80
  }
68
- match SingleMode::new(
69
- cluster_template.server.clone(),
70
- cluster_template.listeners.clone(),
71
- cluster_template.lifecycle_channel.clone(),
72
- ) {
81
+ match SingleMode::new(cluster_template.server_config.clone()) {
73
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
+ }
74
93
  Arc::new(single_mode).run().ok();
75
94
  }
76
95
  Err(e) => {
@@ -83,6 +102,13 @@ impl ProcessWorker {
83
102
  Ok(())
84
103
  }
85
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
+
86
112
  pub(crate) fn memory_usage(&self) -> Option<u64> {
87
113
  if let Some(pid) = *self.child_pid.lock() {
88
114
  let s = System::new_all();
@@ -122,8 +148,14 @@ impl ProcessWorker {
122
148
  pub(crate) async fn graceful_shutdown(&self, cluster_template: Arc<ClusterMode>) {
123
149
  let self_clone = self.clone();
124
150
  self_clone.request_shutdown();
125
- let force_kill_time =
126
- Instant::now() + Duration::from_secs_f64(cluster_template.server.shutdown_timeout);
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
+ );
127
159
  while self_clone.is_alive() && force_kill_time > Instant::now() {
128
160
  tokio::time::sleep(Duration::from_millis(100)).await;
129
161
  }
@@ -155,8 +187,12 @@ impl ProcessWorker {
155
187
  pub(crate) fn request_shutdown(&self) {
156
188
  let child_pid = *self.child_pid.lock();
157
189
  if let Some(pid) = child_pid {
158
- if let Err(e) = kill(pid, SIGTERM) {
159
- error!("Failed to send SIGTERM to process {}: {}", pid, e);
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");
160
196
  }
161
197
  }
162
198
  }
@@ -164,10 +200,25 @@ impl ProcessWorker {
164
200
  pub(crate) fn force_kill(&self) {
165
201
  let child_pid = *self.child_pid.lock();
166
202
  if let Some(pid) = child_pid {
167
- if let Err(e) = kill(pid, SIGKILL) {
168
- error!("Failed to force kill process {}: {}", pid, e);
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);
169
218
  }
170
219
  }
220
+
221
+ Ok(())
171
222
  }
172
223
 
173
224
  pub(crate) fn just_started(&self) -> bool {
@@ -0,0 +1,26 @@
1
+ use http::StatusCode;
2
+ use serde::Deserialize;
3
+
4
+ #[derive(Debug, Clone, Deserialize, Default)]
5
+ pub enum RedirectType {
6
+ #[serde(rename(deserialize = "permanent"))]
7
+ #[default]
8
+ Permanent,
9
+ #[serde(rename(deserialize = "temporary"))]
10
+ Temporary,
11
+ #[serde(rename(deserialize = "found"))]
12
+ Found,
13
+ #[serde(rename(deserialize = "moved_permanently"))]
14
+ MovedPermanently,
15
+ }
16
+
17
+ impl RedirectType {
18
+ pub fn status_code(&self) -> StatusCode {
19
+ match self {
20
+ RedirectType::Permanent => StatusCode::PERMANENT_REDIRECT,
21
+ RedirectType::Temporary => StatusCode::TEMPORARY_REDIRECT,
22
+ RedirectType::Found => StatusCode::FOUND,
23
+ RedirectType::MovedPermanently => StatusCode::MOVED_PERMANENTLY,
24
+ }
25
+ }
26
+ }
@@ -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
+ }