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