itsi-scheduler 0.2.22-aarch64-linux
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 +7 -0
- data/.rubocop.yml +8 -0
- data/Cargo.lock +997 -0
- data/Cargo.toml +7 -0
- data/Rakefile +39 -0
- data/ext/itsi_acme/Cargo.toml +86 -0
- data/ext/itsi_acme/examples/high_level.rs +63 -0
- data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
- data/ext/itsi_acme/examples/low_level.rs +87 -0
- data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
- data/ext/itsi_acme/src/acceptor.rs +81 -0
- data/ext/itsi_acme/src/acme.rs +354 -0
- data/ext/itsi_acme/src/axum.rs +86 -0
- data/ext/itsi_acme/src/cache.rs +39 -0
- data/ext/itsi_acme/src/caches/boxed.rs +80 -0
- data/ext/itsi_acme/src/caches/composite.rs +69 -0
- data/ext/itsi_acme/src/caches/dir.rs +106 -0
- data/ext/itsi_acme/src/caches/mod.rs +11 -0
- data/ext/itsi_acme/src/caches/no.rs +78 -0
- data/ext/itsi_acme/src/caches/test.rs +136 -0
- data/ext/itsi_acme/src/config.rs +172 -0
- data/ext/itsi_acme/src/https_helper.rs +69 -0
- data/ext/itsi_acme/src/incoming.rs +142 -0
- data/ext/itsi_acme/src/jose.rs +161 -0
- data/ext/itsi_acme/src/lib.rs +142 -0
- data/ext/itsi_acme/src/resolver.rs +59 -0
- data/ext/itsi_acme/src/state.rs +424 -0
- data/ext/itsi_error/Cargo.lock +368 -0
- data/ext/itsi_error/Cargo.toml +12 -0
- data/ext/itsi_error/src/lib.rs +140 -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.lock +355 -0
- data/ext/itsi_rb_helpers/Cargo.toml +11 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +232 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/extconf.rb +11 -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 +320 -0
- data/ext/itsi_scheduler/src/lib.rs +39 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +94 -0
- data/ext/itsi_server/src/default_responses/mod.rs +14 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +154 -0
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +116 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +149 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +346 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +265 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +399 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +447 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +545 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +650 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +102 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
- data/ext/itsi_server/src/server/binds/bind.rs +204 -0
- data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/binds/listener.rs +485 -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 +278 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/frame_stream.rs +143 -0
- data/ext/itsi_server/src/server/http_message_types.rs +230 -0
- data/ext/itsi_server/src/server/io_stream.rs +128 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +170 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +63 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +94 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +343 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +151 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +329 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +193 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +64 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +188 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +168 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +183 -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 +209 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +133 -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 +122 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +407 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +155 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +54 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +54 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +51 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +138 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +269 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +62 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +218 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +31 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +381 -0
- data/ext/itsi_server/src/server/mod.rs +14 -0
- data/ext/itsi_server/src/server/process_worker.rs +247 -0
- data/ext/itsi_server/src/server/redirect_type.rs +26 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +411 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +31 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +449 -0
- data/ext/itsi_server/src/server/signal.rs +129 -0
- data/ext/itsi_server/src/server/size_limited_incoming.rs +107 -0
- data/ext/itsi_server/src/server/thread_worker.rs +504 -0
- data/ext/itsi_server/src/services/cache_store.rs +74 -0
- data/ext/itsi_server/src/services/itsi_http_service.rs +270 -0
- data/ext/itsi_server/src/services/mime_types.rs +2896 -0
- data/ext/itsi_server/src/services/mod.rs +6 -0
- data/ext/itsi_server/src/services/password_hasher.rs +89 -0
- data/ext/itsi_server/src/services/rate_limiter.rs +609 -0
- data/ext/itsi_server/src/services/static_file_server.rs +1400 -0
- data/ext/itsi_tracing/Cargo.lock +274 -0
- data/ext/itsi_tracing/Cargo.toml +17 -0
- data/ext/itsi_tracing/src/lib.rs +370 -0
- data/itsi-scheduler-100.png +0 -0
- data/lib/itsi/schedule_refinement.rb +96 -0
- data/lib/itsi/scheduler/3.1/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.2/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.3/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.4/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/4.0/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/native_extension.rb +34 -0
- data/lib/itsi/scheduler/version.rb +7 -0
- data/lib/itsi/scheduler.rb +153 -0
- data/vendor/rb-sys-build/.cargo-ok +1 -0
- data/vendor/rb-sys-build/.cargo_vcs_info.json +6 -0
- data/vendor/rb-sys-build/Cargo.lock +294 -0
- data/vendor/rb-sys-build/Cargo.toml +71 -0
- data/vendor/rb-sys-build/Cargo.toml.orig +32 -0
- data/vendor/rb-sys-build/LICENSE-APACHE +190 -0
- data/vendor/rb-sys-build/LICENSE-MIT +21 -0
- data/vendor/rb-sys-build/src/bindings/sanitizer.rs +185 -0
- data/vendor/rb-sys-build/src/bindings/stable_api.rs +247 -0
- data/vendor/rb-sys-build/src/bindings/wrapper.h +71 -0
- data/vendor/rb-sys-build/src/bindings.rs +280 -0
- data/vendor/rb-sys-build/src/cc.rs +421 -0
- data/vendor/rb-sys-build/src/lib.rs +12 -0
- data/vendor/rb-sys-build/src/rb_config/flags.rs +101 -0
- data/vendor/rb-sys-build/src/rb_config/library.rs +132 -0
- data/vendor/rb-sys-build/src/rb_config/search_path.rs +57 -0
- data/vendor/rb-sys-build/src/rb_config.rs +906 -0
- data/vendor/rb-sys-build/src/utils.rs +53 -0
- metadata +210 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
use serde::Deserialize;
|
|
2
|
+
use std::sync::OnceLock;
|
|
3
|
+
|
|
4
|
+
use crate::{
|
|
5
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
|
6
|
+
services::itsi_http_service::HttpRequestContext,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
10
|
+
#[serde(transparent)]
|
|
11
|
+
pub struct StringRewrite {
|
|
12
|
+
pub template_string: String,
|
|
13
|
+
#[serde(default)]
|
|
14
|
+
pub segments: OnceLock<Vec<Segment>>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
18
|
+
pub enum Segment {
|
|
19
|
+
Literal(String),
|
|
20
|
+
Placeholder(String),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn parse_template(template: &str) -> Vec<Segment> {
|
|
24
|
+
let mut segments = Vec::new();
|
|
25
|
+
let mut last_index = 0;
|
|
26
|
+
while let Some(start_index) = template[last_index..].find('{') {
|
|
27
|
+
let start_index = last_index + start_index;
|
|
28
|
+
// Add the literal text before the placeholder.
|
|
29
|
+
if start_index > last_index {
|
|
30
|
+
segments.push(Segment::Literal(
|
|
31
|
+
template[last_index..start_index].to_string(),
|
|
32
|
+
));
|
|
33
|
+
}
|
|
34
|
+
// Find the corresponding closing brace.
|
|
35
|
+
if let Some(end_index) = template[start_index..].find('}') {
|
|
36
|
+
let end_index = start_index + end_index;
|
|
37
|
+
let placeholder = &template[start_index + 1..end_index];
|
|
38
|
+
segments.push(Segment::Placeholder(placeholder.to_string()));
|
|
39
|
+
last_index = end_index + 1;
|
|
40
|
+
} else {
|
|
41
|
+
// No closing brace found; treat the rest as literal.
|
|
42
|
+
segments.push(Segment::Literal(template[start_index..].to_string()));
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if last_index < template.len() {
|
|
47
|
+
segments.push(Segment::Literal(template[last_index..].to_string()));
|
|
48
|
+
}
|
|
49
|
+
segments
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl StringRewrite {
|
|
53
|
+
/// Apply a single modifier of the form `op:arg` (or for replace `op:from,to`)
|
|
54
|
+
#[inline]
|
|
55
|
+
fn apply_modifier(s: &mut String, mod_str: &str) {
|
|
56
|
+
if let Some((op, arg)) = mod_str.split_once(':') {
|
|
57
|
+
match op {
|
|
58
|
+
"strip_prefix" => {
|
|
59
|
+
if s.starts_with(arg) {
|
|
60
|
+
let _ = s.drain(..arg.len());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
"strip_suffix" => {
|
|
64
|
+
if s.ends_with(arg) {
|
|
65
|
+
let len = s.len();
|
|
66
|
+
let start = len.saturating_sub(arg.len());
|
|
67
|
+
let _ = s.drain(start..);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
"replace" => {
|
|
71
|
+
if let Some((from, to)) = arg.split_once(',') {
|
|
72
|
+
if s.contains(from) {
|
|
73
|
+
*s = s.replace(from, to);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
_ => {}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub fn rewrite_request(&self, req: &HttpRequest, context: &HttpRequestContext) -> String {
|
|
83
|
+
let segments = self
|
|
84
|
+
.segments
|
|
85
|
+
.get_or_init(|| parse_template(&self.template_string));
|
|
86
|
+
let captures = context
|
|
87
|
+
.matching_pattern
|
|
88
|
+
.as_ref()
|
|
89
|
+
.and_then(|re| re.captures(req.uri().path()));
|
|
90
|
+
|
|
91
|
+
let mut result = String::with_capacity(self.template_string.len());
|
|
92
|
+
|
|
93
|
+
for segment in segments {
|
|
94
|
+
match segment {
|
|
95
|
+
Segment::Literal(text) => {
|
|
96
|
+
result.push_str(text);
|
|
97
|
+
}
|
|
98
|
+
Segment::Placeholder(raw) => {
|
|
99
|
+
// split into key and optional modifier
|
|
100
|
+
let mut parts = raw.split('|');
|
|
101
|
+
let key = parts.next().unwrap();
|
|
102
|
+
let modifiers = parts; // zero o
|
|
103
|
+
|
|
104
|
+
// 1) lookup the raw replacement
|
|
105
|
+
let mut replacement = match key {
|
|
106
|
+
"request_id" => context.short_request_id(),
|
|
107
|
+
"request_id_full" => context.request_id(),
|
|
108
|
+
"method" => req.method().as_str().to_string(),
|
|
109
|
+
"path" => req.uri().path().to_string(),
|
|
110
|
+
"addr" => context.addr.to_owned(),
|
|
111
|
+
"host" => req.uri().host().unwrap_or("localhost").to_string(),
|
|
112
|
+
"path_and_query" => req
|
|
113
|
+
.uri()
|
|
114
|
+
.path_and_query()
|
|
115
|
+
.map(|pq| pq.to_string())
|
|
116
|
+
.unwrap_or_default(),
|
|
117
|
+
"query" => {
|
|
118
|
+
let q = req.uri().query().unwrap_or("");
|
|
119
|
+
if q.is_empty() {
|
|
120
|
+
"".to_string()
|
|
121
|
+
} else {
|
|
122
|
+
format!("?{}", q)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
"port" => req
|
|
126
|
+
.uri()
|
|
127
|
+
.port()
|
|
128
|
+
.map(|p| p.to_string())
|
|
129
|
+
.unwrap_or_else(|| "80".to_string()),
|
|
130
|
+
"start_time" => {
|
|
131
|
+
if let Some(ts) = context.start_time() {
|
|
132
|
+
ts.format("%Y-%m-%d:%H:%M:%S:%3f").to_string()
|
|
133
|
+
} else {
|
|
134
|
+
"N/A".to_string()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
other => {
|
|
138
|
+
// headers first
|
|
139
|
+
if let Some(hv) = req.headers().get(other) {
|
|
140
|
+
hv.to_str().unwrap_or("").to_string()
|
|
141
|
+
}
|
|
142
|
+
// then any regex‐capture
|
|
143
|
+
else if let Some(caps) = &captures {
|
|
144
|
+
caps.name(other)
|
|
145
|
+
.map(|m| m.as_str().to_string())
|
|
146
|
+
.unwrap_or_else(|| format!("{{{}}}", other))
|
|
147
|
+
}
|
|
148
|
+
// fallback: leave placeholder intact
|
|
149
|
+
else {
|
|
150
|
+
format!("{{{}}}", other)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
for m in modifiers {
|
|
156
|
+
Self::apply_modifier(&mut replacement, m);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
result.push_str(&replacement);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
result
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pub fn rewrite_response(&self, resp: &HttpResponse, context: &HttpRequestContext) -> String {
|
|
168
|
+
let segments = self
|
|
169
|
+
.segments
|
|
170
|
+
.get_or_init(|| parse_template(&self.template_string));
|
|
171
|
+
|
|
172
|
+
let mut result = String::with_capacity(self.template_string.len());
|
|
173
|
+
for segment in segments {
|
|
174
|
+
match segment {
|
|
175
|
+
Segment::Literal(text) => {
|
|
176
|
+
result.push_str(text);
|
|
177
|
+
}
|
|
178
|
+
Segment::Placeholder(raw) => {
|
|
179
|
+
let mut parts = raw.split('|');
|
|
180
|
+
let key = parts.next().unwrap();
|
|
181
|
+
let modifiers = parts; // zero o
|
|
182
|
+
|
|
183
|
+
let mut replacement = match key {
|
|
184
|
+
"request_id" => context.short_request_id(),
|
|
185
|
+
"request_id_full" => context.request_id(),
|
|
186
|
+
"status" => resp.status().as_str().to_string(),
|
|
187
|
+
"addr" => context.addr.to_owned(),
|
|
188
|
+
"response_time" => {
|
|
189
|
+
let dur = context.get_response_time();
|
|
190
|
+
let micros = dur.as_micros();
|
|
191
|
+
if micros < 1_000 {
|
|
192
|
+
format!("{}µs", micros)
|
|
193
|
+
} else {
|
|
194
|
+
let ms = dur.as_secs_f64() * 1_000.0;
|
|
195
|
+
format!("{:.3}ms", ms)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
other => {
|
|
199
|
+
if let Some(hv) = resp.headers().get(other) {
|
|
200
|
+
hv.to_str().unwrap_or("").to_string()
|
|
201
|
+
} else {
|
|
202
|
+
format!("{{{}}}", other)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
for m in modifiers {
|
|
208
|
+
Self::apply_modifier(&mut replacement, m);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
result.push_str(&replacement);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
result
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
use serde::{Deserialize, Serialize};
|
|
2
|
+
|
|
3
|
+
use crate::server::http_message_types::{HttpRequest, RequestExt};
|
|
4
|
+
|
|
5
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
6
|
+
pub enum TokenSource {
|
|
7
|
+
#[serde(rename(deserialize = "header"))]
|
|
8
|
+
Header {
|
|
9
|
+
name: String,
|
|
10
|
+
prefix: Option<String>,
|
|
11
|
+
},
|
|
12
|
+
#[serde(rename(deserialize = "query"))]
|
|
13
|
+
Query(String),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl TokenSource {
|
|
17
|
+
pub fn extract_token<'req>(&self, req: &'req HttpRequest) -> Option<&'req str> {
|
|
18
|
+
match self {
|
|
19
|
+
TokenSource::Header { name, prefix } => req.headers().get(name).and_then(|value| {
|
|
20
|
+
value.to_str().ok().and_then(|value| {
|
|
21
|
+
if let Some(prefix) = prefix {
|
|
22
|
+
value.strip_prefix(prefix).map(|v| v.trim())
|
|
23
|
+
} else {
|
|
24
|
+
Some(value)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}),
|
|
28
|
+
TokenSource::Query(query_name) => req.query_param(query_name),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -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::Ruby::get().unwrap().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::Ruby::get().unwrap().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::Ruby::get().unwrap().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::Ruby::get().unwrap().exception_standard_error(),
|
|
163
|
+
"Route is missing middleware key",
|
|
164
|
+
))?)
|
|
165
|
+
.ok_or(magnus::Error::new(
|
|
166
|
+
magnus::Ruby::get().unwrap().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::Ruby::get().unwrap().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::Ruby::get().unwrap().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::Ruby::get().unwrap().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::Ruby::get().unwrap().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::Ruby::get().unwrap().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::Ruby::get().unwrap().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::Ruby::get().unwrap().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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
pub mod binds;
|
|
2
|
+
pub mod byte_frame;
|
|
3
|
+
pub mod frame_stream;
|
|
4
|
+
pub mod http_message_types;
|
|
5
|
+
pub mod io_stream;
|
|
6
|
+
pub mod lifecycle_event;
|
|
7
|
+
pub mod middleware_stack;
|
|
8
|
+
pub mod process_worker;
|
|
9
|
+
pub mod redirect_type;
|
|
10
|
+
pub mod request_job;
|
|
11
|
+
pub mod serve_strategy;
|
|
12
|
+
pub mod signal;
|
|
13
|
+
pub mod size_limited_incoming;
|
|
14
|
+
pub mod thread_worker;
|