itsi-server 0.2.21.rc1 → 0.2.21.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +12 -12
- data/ext/itsi_error/Cargo.toml +1 -1
- data/ext/itsi_rb_helpers/Cargo.toml +2 -2
- data/ext/itsi_rb_helpers/src/heap_value.rs +2 -2
- data/ext/itsi_rb_helpers/src/lib.rs +9 -5
- data/ext/itsi_scheduler/Cargo.toml +2 -2
- data/ext/itsi_server/Cargo.toml +2 -2
- data/ext/itsi_server/src/lib.rs +3 -2
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +34 -18
- data/ext/itsi_server/src/server/frame_stream.rs +2 -1
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +8 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +90 -21
- data/ext/itsi_server/src/services/password_hasher.rs +8 -2
- data/ext/itsi_server/src/services/rate_limiter.rs +72 -25
- data/ext/itsi_server/src/services/static_file_server.rs +35 -9
- data/ext/itsi_server/target/release/build/clang-sys-0dae18670e690c25/out/common.rs +355 -0
- data/ext/itsi_server/target/release/build/clang-sys-0dae18670e690c25/out/dynamic.rs +276 -0
- data/ext/itsi_server/target/release/build/clang-sys-0dae18670e690c25/out/macros.rs +49 -0
- data/ext/itsi_server/target/release/build/oid-registry-71b994a322b296ec/out/oid_db.rs +537 -0
- data/ext/itsi_server/target/release/build/rb-sys-9f9831ab50fb86db/out/bindings-0.9.124-mri-arm64-darwin24-2.7.8.rs +6234 -0
- data/ext/itsi_server/target/release/build/rb-sys-9f9831ab50fb86db/out/bindings-0.9.124-mri-arm64-darwin24-3.4.5.rs +8936 -0
- data/ext/itsi_server/target/release/build/rb-sys-9f9831ab50fb86db/out/bindings-0.9.124-mri-arm64-darwin24-4.0.1.rs +9060 -0
- data/ext/itsi_server/target/release/build/typenum-11265e44e46de3b7/out/tests.rs +20563 -0
- data/ext/itsi_tracing/src/lib.rs +3 -3
- data/lib/itsi/http_request.rb +22 -17
- data/lib/itsi/rack_env_pool.rb +7 -17
- data/lib/itsi/server/config/middleware/static_assets.rb +8 -1
- data/lib/itsi/server/rack_interface.rb +12 -0
- data/lib/itsi/server/version.rb +1 -1
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b429e81e1fd3091d28a61a2db0c55ea8d38965077c4da330470c248f8a7f39c
|
|
4
|
+
data.tar.gz: c6bb3a7a3ac05b5d1a8b75d97774b6576717e900dae56e33bae11b70cf6ea782
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cfc08df98fb6faad6c36e240f96e10e9c49f6ffc1f87c4fd874317ea382cf4a27dd06136844d8a3831781c308cac0c49f60ad870e66ad88e09659fd4e10b4a93
|
|
7
|
+
data.tar.gz: 2b09c90b8079d16998a213f6fdb49cb655463ea6d0c487fafd9c590b0a54c1a64fc3ed360a3ffb7bfbc3135cc5c7e2310f21894de4c3b08fa38d3a7bf1142613
|
data/Cargo.lock
CHANGED
|
@@ -1906,9 +1906,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
|
|
1906
1906
|
|
|
1907
1907
|
[[package]]
|
|
1908
1908
|
name = "magnus"
|
|
1909
|
-
version = "0.
|
|
1909
|
+
version = "0.8.2"
|
|
1910
1910
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1911
|
-
checksum = "
|
|
1911
|
+
checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
|
|
1912
1912
|
dependencies = [
|
|
1913
1913
|
"bytes",
|
|
1914
1914
|
"magnus-macros",
|
|
@@ -1919,9 +1919,9 @@ dependencies = [
|
|
|
1919
1919
|
|
|
1920
1920
|
[[package]]
|
|
1921
1921
|
name = "magnus-macros"
|
|
1922
|
-
version = "0.
|
|
1922
|
+
version = "0.8.0"
|
|
1923
1923
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1924
|
-
checksum = "
|
|
1924
|
+
checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
|
|
1925
1925
|
dependencies = [
|
|
1926
1926
|
"proc-macro2",
|
|
1927
1927
|
"quote",
|
|
@@ -2554,18 +2554,18 @@ dependencies = [
|
|
|
2554
2554
|
|
|
2555
2555
|
[[package]]
|
|
2556
2556
|
name = "rb-sys"
|
|
2557
|
-
version = "0.9.
|
|
2557
|
+
version = "0.9.124"
|
|
2558
2558
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2559
|
-
checksum = "
|
|
2559
|
+
checksum = "c85c4188462601e2aa1469def389c17228566f82ea72f137ed096f21591bc489"
|
|
2560
2560
|
dependencies = [
|
|
2561
2561
|
"rb-sys-build",
|
|
2562
2562
|
]
|
|
2563
2563
|
|
|
2564
2564
|
[[package]]
|
|
2565
2565
|
name = "rb-sys-build"
|
|
2566
|
-
version = "0.9.
|
|
2566
|
+
version = "0.9.124"
|
|
2567
2567
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2568
|
-
checksum = "
|
|
2568
|
+
checksum = "568068db4102230882e6d4ae8de6632e224ca75fe5970f6e026a04e91ed635d3"
|
|
2569
2569
|
dependencies = [
|
|
2570
2570
|
"bindgen",
|
|
2571
2571
|
"lazy_static",
|
|
@@ -2578,9 +2578,9 @@ dependencies = [
|
|
|
2578
2578
|
|
|
2579
2579
|
[[package]]
|
|
2580
2580
|
name = "rb-sys-env"
|
|
2581
|
-
version = "0.
|
|
2581
|
+
version = "0.2.3"
|
|
2582
2582
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2583
|
-
checksum = "
|
|
2583
|
+
checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
|
|
2584
2584
|
|
|
2585
2585
|
[[package]]
|
|
2586
2586
|
name = "rcgen"
|
|
@@ -2972,9 +2972,9 @@ dependencies = [
|
|
|
2972
2972
|
|
|
2973
2973
|
[[package]]
|
|
2974
2974
|
name = "serde_magnus"
|
|
2975
|
-
version = "0.
|
|
2975
|
+
version = "0.11.0"
|
|
2976
2976
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2977
|
-
checksum = "
|
|
2977
|
+
checksum = "8ff64c88ddd26acdcad5a501f18bcc339927b77b69f4a03bfaf2a6fc5ba2ac4b"
|
|
2978
2978
|
dependencies = [
|
|
2979
2979
|
"magnus",
|
|
2980
2980
|
"serde",
|
data/ext/itsi_error/Cargo.toml
CHANGED
|
@@ -5,7 +5,7 @@ edition = "2021"
|
|
|
5
5
|
|
|
6
6
|
[dependencies]
|
|
7
7
|
cfg-if = "1.0.0"
|
|
8
|
-
magnus = { version = "0.
|
|
8
|
+
magnus = { version = "0.8.2", features = ["rb-sys", "bytes"] }
|
|
9
9
|
nix = "0.29.0"
|
|
10
|
-
rb-sys = "0.9.
|
|
10
|
+
rb-sys = "0.9.117"
|
|
11
11
|
serde = "1.0.219"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
use magnus::IntoValue;
|
|
2
1
|
use magnus::rb_sys::AsRawValue;
|
|
3
2
|
use magnus::value::BoxValue;
|
|
4
|
-
use magnus::
|
|
3
|
+
use magnus::IntoValue;
|
|
4
|
+
use magnus::{value::ReprValue, Ruby, Value};
|
|
5
5
|
use std::fmt::{self, Debug, Formatter};
|
|
6
6
|
use std::ops::Deref;
|
|
7
7
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
use std::{ffi::c_int, os::raw::c_void, ptr::null_mut};
|
|
2
2
|
|
|
3
3
|
use magnus::{
|
|
4
|
-
ArgList, RArray, Ruby, Thread, Value,
|
|
5
4
|
block::Proc,
|
|
6
|
-
rb_sys::{AsRawId, FromRawValue
|
|
5
|
+
rb_sys::{protect, AsRawId, FromRawValue},
|
|
7
6
|
value::{IntoId, LazyId, ReprValue},
|
|
7
|
+
ArgList, RArray, Ruby, Thread, Value,
|
|
8
8
|
};
|
|
9
9
|
use rb_sys::{
|
|
10
|
-
|
|
11
|
-
rb_thread_schedule, rb_thread_wakeup,
|
|
10
|
+
rb_funcallv, rb_thread_call_with_gvl, rb_thread_call_without_gvl, rb_thread_create,
|
|
11
|
+
rb_thread_schedule, rb_thread_wakeup, VALUE,
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
mod heap_value;
|
|
@@ -182,7 +182,11 @@ pub fn terminate_non_fork_safe_threads() {
|
|
|
182
182
|
&& !v_thread
|
|
183
183
|
.funcall::<_, _, bool>(*ID_THREAD_VARIABLE_GET, (ruby.sym_new("fork_safe"),))
|
|
184
184
|
.unwrap_or(false);
|
|
185
|
-
if non_fork_safe {
|
|
185
|
+
if non_fork_safe {
|
|
186
|
+
Some(v_thread)
|
|
187
|
+
} else {
|
|
188
|
+
None
|
|
189
|
+
}
|
|
186
190
|
})
|
|
187
191
|
.collect::<Vec<_>>();
|
|
188
192
|
|
|
@@ -10,7 +10,7 @@ publish = false
|
|
|
10
10
|
crate-type = ["cdylib"]
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
|
-
magnus = { version = "0.
|
|
13
|
+
magnus = { version = "0.8.2", features = ["rb-sys", "bytes"] }
|
|
14
14
|
derive_more = { version = "2.0.1", features = ["debug"] }
|
|
15
15
|
itsi_tracing = { path = "../itsi_tracing" }
|
|
16
16
|
itsi_rb_helpers = { path = "../itsi_rb_helpers" }
|
|
@@ -18,7 +18,7 @@ itsi_error = { path = "../itsi_error" }
|
|
|
18
18
|
itsi_instrument_entry = { path = "../itsi_instrument_entry" }
|
|
19
19
|
parking_lot = "0.12.3"
|
|
20
20
|
mio = { version = "1.0.3", features = ["os-poll", "os-ext"] }
|
|
21
|
-
rb-sys = "0.9.
|
|
21
|
+
rb-sys = "0.9.117"
|
|
22
22
|
bytes = "1.10.1"
|
|
23
23
|
nix = "0.29.0"
|
|
24
24
|
tracing = "0.1.41"
|
data/ext/itsi_server/Cargo.toml
CHANGED
|
@@ -42,7 +42,7 @@ itsi_rb_helpers = { path = "../itsi_rb_helpers" }
|
|
|
42
42
|
itsi_tracing = { path = "../itsi_tracing" }
|
|
43
43
|
itsi_acme = { path = "../itsi_acme" }
|
|
44
44
|
jsonwebtoken = "9.3.1"
|
|
45
|
-
magnus = { version = "0.
|
|
45
|
+
magnus = { version = "0.8.2", features = ["bytes", "rb-sys"] }
|
|
46
46
|
notify = { version = "8.0.0" }
|
|
47
47
|
nix = { version = "0.29.0", features = [
|
|
48
48
|
"socket",
|
|
@@ -72,7 +72,7 @@ rustls = "0.23.23"
|
|
|
72
72
|
rustls-pemfile = "2.2.0"
|
|
73
73
|
serde = "1.0.219"
|
|
74
74
|
serde_json = "1.0.140"
|
|
75
|
-
serde_magnus = "0.
|
|
75
|
+
serde_magnus = "0.11.0"
|
|
76
76
|
sha2 = "0.10.8"
|
|
77
77
|
socket2 = "0.5.8"
|
|
78
78
|
sysinfo = "0.33.1"
|
data/ext/itsi_server/src/lib.rs
CHANGED
|
@@ -7,7 +7,7 @@ pub mod ruby_types;
|
|
|
7
7
|
pub mod server;
|
|
8
8
|
pub mod services;
|
|
9
9
|
|
|
10
|
-
use magnus::{error::Result, function, method, Module, Object, Ruby};
|
|
10
|
+
use magnus::{error::Result, function, method, Class, Module, Object, Ruby};
|
|
11
11
|
use prelude::*;
|
|
12
12
|
use ruby_types::{
|
|
13
13
|
itsi_body_proxy::ItsiBodyProxy, itsi_grpc_call::ItsiGrpcCall,
|
|
@@ -36,7 +36,8 @@ fn init(ruby: &Ruby) -> Result<()> {
|
|
|
36
36
|
)?;
|
|
37
37
|
|
|
38
38
|
let server = ruby.get_inner(&ITSI_SERVER);
|
|
39
|
-
server.
|
|
39
|
+
server.define_alloc_func::<ItsiServer>();
|
|
40
|
+
server.define_method("initialize", method!(ItsiServer::initialize, 3))?;
|
|
40
41
|
server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
|
|
41
42
|
server.define_method("start", method!(ItsiServer::start, 0))?;
|
|
42
43
|
server.define_method("stop", method!(ItsiServer::stop, 0))?;
|
|
@@ -13,26 +13,32 @@ use tracing::{info, instrument};
|
|
|
13
13
|
mod file_watcher;
|
|
14
14
|
pub mod itsi_server_config;
|
|
15
15
|
#[magnus::wrap(class = "Itsi::Server", free_immediately, size)]
|
|
16
|
-
#[derive(Clone)]
|
|
16
|
+
#[derive(Clone, Default)]
|
|
17
17
|
pub struct ItsiServer {
|
|
18
|
-
pub config: Arc<Mutex<Arc<ItsiServerConfig
|
|
18
|
+
pub config: Arc<Mutex<Option<Arc<ItsiServerConfig>>>>,
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
impl ItsiServer {
|
|
22
|
-
pub fn
|
|
23
|
-
|
|
22
|
+
pub fn initialize(
|
|
23
|
+
&self,
|
|
24
24
|
cli_params: RHash,
|
|
25
25
|
itsifile_path: Option<PathBuf>,
|
|
26
26
|
itsi_config_proc: Option<Proc>,
|
|
27
|
-
) -> Result<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
) -> Result<()> {
|
|
28
|
+
let ruby = Ruby::get().map_err(|_| {
|
|
29
|
+
magnus::Error::new(
|
|
30
|
+
magnus::exception::runtime_error(),
|
|
31
|
+
"Failed to acquire Ruby VM handle",
|
|
32
|
+
)
|
|
33
|
+
})?;
|
|
34
|
+
let config = Arc::new(ItsiServerConfig::new(
|
|
35
|
+
&ruby,
|
|
36
|
+
cli_params,
|
|
37
|
+
itsifile_path,
|
|
38
|
+
itsi_config_proc,
|
|
39
|
+
)?);
|
|
40
|
+
*self.config.lock() = Some(config);
|
|
41
|
+
Ok(())
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
pub fn stop(&self) -> Result<()> {
|
|
@@ -40,10 +46,20 @@ impl ItsiServer {
|
|
|
40
46
|
Ok(())
|
|
41
47
|
}
|
|
42
48
|
|
|
49
|
+
fn config(&self) -> Result<Arc<ItsiServerConfig>> {
|
|
50
|
+
self.config.lock().as_ref().cloned().ok_or_else(|| {
|
|
51
|
+
magnus::Error::new(
|
|
52
|
+
magnus::exception::runtime_error(),
|
|
53
|
+
"Itsi::Server not initialized",
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
#[instrument(skip(self))]
|
|
44
59
|
pub fn start(&self) -> Result<()> {
|
|
45
|
-
self.config
|
|
46
|
-
|
|
60
|
+
let server_config = self.config()?;
|
|
61
|
+
server_config.server_params.read().setup_listeners()?;
|
|
62
|
+
let result = if server_config.server_params.read().silence {
|
|
47
63
|
run_silently(|| self.build_and_run_strategy())
|
|
48
64
|
} else {
|
|
49
65
|
info!("Itsi - Rolling into action. ⚪💨");
|
|
@@ -60,11 +76,11 @@ impl ItsiServer {
|
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
pub(crate) fn build_strategy(&self) -> Result<ServeStrategy> {
|
|
63
|
-
let server_config = self.config
|
|
79
|
+
let server_config = self.config()?;
|
|
64
80
|
Ok(if server_config.server_params.read().workers > 1 {
|
|
65
|
-
ServeStrategy::Cluster(Arc::new(ClusterMode::new(server_config
|
|
81
|
+
ServeStrategy::Cluster(Arc::new(ClusterMode::new(server_config)))
|
|
66
82
|
} else {
|
|
67
|
-
ServeStrategy::Single(Arc::new(SingleMode::new(server_config
|
|
83
|
+
ServeStrategy::Single(Arc::new(SingleMode::new(server_config, 0)?))
|
|
68
84
|
})
|
|
69
85
|
}
|
|
70
86
|
|
|
@@ -48,9 +48,10 @@ impl Stream for FrameStream {
|
|
|
48
48
|
if this.shutdown_rx.has_changed().unwrap_or(false)
|
|
49
49
|
&& *this.shutdown_rx.borrow() == RunningPhase::ShutdownPending
|
|
50
50
|
{
|
|
51
|
-
|
|
51
|
+
if let Ok(bytes) = this.receiver.try_recv() {
|
|
52
52
|
return Poll::Ready(Some(Ok(bytes)));
|
|
53
53
|
}
|
|
54
|
+
|
|
54
55
|
this.drained = true;
|
|
55
56
|
return Poll::Ready(None);
|
|
56
57
|
}
|
|
@@ -43,7 +43,7 @@ pub use error_response::ErrorResponse;
|
|
|
43
43
|
pub use etag::ETag;
|
|
44
44
|
pub use intrusion_protection::IntrusionProtection;
|
|
45
45
|
pub use log_requests::LogRequests;
|
|
46
|
-
use magnus::error::Result;
|
|
46
|
+
use magnus::{error::Result, Ruby};
|
|
47
47
|
use magnus::rb_sys::AsRawValue;
|
|
48
48
|
use magnus::Value;
|
|
49
49
|
pub use max_body::MaxBody;
|
|
@@ -88,7 +88,13 @@ pub trait FromValue: Sized + Send + Sync + 'static {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
let
|
|
91
|
+
let ruby = Ruby::get().map_err(|_| {
|
|
92
|
+
magnus::Error::new(
|
|
93
|
+
magnus::exception::runtime_error(),
|
|
94
|
+
"Failed to acquire Ruby VM handle",
|
|
95
|
+
)
|
|
96
|
+
})?;
|
|
97
|
+
let deserialized: Arc<Self> = Arc::new(deserialize(&ruby, value)?);
|
|
92
98
|
cache.insert(raw, deserialized.clone());
|
|
93
99
|
Ok(deserialized)
|
|
94
100
|
}
|
|
@@ -27,6 +27,59 @@ use std::{
|
|
|
27
27
|
};
|
|
28
28
|
use tracing::debug;
|
|
29
29
|
|
|
30
|
+
/// Compact representation of the client's Accept-Encoding preferences.
|
|
31
|
+
/// Priority order is determined by the bit checks in `pick_encoding`.
|
|
32
|
+
#[derive(Clone, Copy, Debug, Default)]
|
|
33
|
+
struct AcceptEncodingMask(u8);
|
|
34
|
+
|
|
35
|
+
impl AcceptEncodingMask {
|
|
36
|
+
const BR: u8 = 1 << 0;
|
|
37
|
+
const GZIP: u8 = 1 << 1;
|
|
38
|
+
const ZSTD: u8 = 1 << 2;
|
|
39
|
+
const DEFLATE: u8 = 1 << 3;
|
|
40
|
+
|
|
41
|
+
fn from_headers(headers: &[HeaderValue]) -> Self {
|
|
42
|
+
let mut mask = 0u8;
|
|
43
|
+
|
|
44
|
+
for hv in headers {
|
|
45
|
+
let Ok(s) = hv.to_str() else { continue };
|
|
46
|
+
|
|
47
|
+
// We intentionally ignore q-values and treat any mention as "acceptable".
|
|
48
|
+
// This is a fast-path optimization for common benchmark/client headers.
|
|
49
|
+
for part in s.split(',') {
|
|
50
|
+
let token = part.split(';').next().unwrap_or("").trim();
|
|
51
|
+
match token {
|
|
52
|
+
"br" => mask |= Self::BR,
|
|
53
|
+
"gzip" => mask |= Self::GZIP,
|
|
54
|
+
"zstd" => mask |= Self::ZSTD,
|
|
55
|
+
"deflate" => mask |= Self::DEFLATE,
|
|
56
|
+
_ => {}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Self(mask)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn pick_encoding(self) -> Option<&'static str> {
|
|
65
|
+
// Prefer stronger/faster compression if available.
|
|
66
|
+
// (Actual availability is checked by the file server.)
|
|
67
|
+
if (self.0 & Self::ZSTD) != 0 {
|
|
68
|
+
return Some("zstd");
|
|
69
|
+
}
|
|
70
|
+
if (self.0 & Self::BR) != 0 {
|
|
71
|
+
return Some("br");
|
|
72
|
+
}
|
|
73
|
+
if (self.0 & Self::GZIP) != 0 {
|
|
74
|
+
return Some("gzip");
|
|
75
|
+
}
|
|
76
|
+
if (self.0 & Self::DEFLATE) != 0 {
|
|
77
|
+
return Some("deflate");
|
|
78
|
+
}
|
|
79
|
+
None
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
30
83
|
#[derive(Debug, Deserialize)]
|
|
31
84
|
pub struct StaticAssets {
|
|
32
85
|
pub root_dir: PathBuf,
|
|
@@ -98,7 +151,10 @@ impl MiddlewareLayer for StaticAssets {
|
|
|
98
151
|
return Ok(Either::Left(req));
|
|
99
152
|
}
|
|
100
153
|
|
|
154
|
+
// We still populate the context cache for any other middleware that might want it,
|
|
155
|
+
// but we avoid re-parsing Accept-Encoding later by computing a compact mask here.
|
|
101
156
|
context.set_supported_encoding_set(&req);
|
|
157
|
+
|
|
102
158
|
let abs_path = req.uri().path();
|
|
103
159
|
let rel_path = if !self.relative_path {
|
|
104
160
|
abs_path.trim_start_matches("/")
|
|
@@ -119,7 +175,6 @@ impl MiddlewareLayer for StaticAssets {
|
|
|
119
175
|
};
|
|
120
176
|
|
|
121
177
|
debug!(target: "middleware::static_assets", "Asset path is {}", rel_path);
|
|
122
|
-
// Determine if this is a HEAD request
|
|
123
178
|
let is_head_request = req.method() == Method::HEAD;
|
|
124
179
|
|
|
125
180
|
// Extract range and if-modified-since headers
|
|
@@ -135,6 +190,22 @@ impl MiddlewareLayer for StaticAssets {
|
|
|
135
190
|
let encodings: &[HeaderValue] = context
|
|
136
191
|
.supported_encoding_set()
|
|
137
192
|
.map_or(&[], |set| set.as_slice());
|
|
193
|
+
|
|
194
|
+
// Compute a fast encoding preference and narrow the encoding list we hand to the server.
|
|
195
|
+
// This avoids repeated per-request string splitting/trim in the static file server.
|
|
196
|
+
let mask = AcceptEncodingMask::from_headers(encodings);
|
|
197
|
+
let preferred = mask.pick_encoding();
|
|
198
|
+
|
|
199
|
+
let narrowed: [HeaderValue; 1];
|
|
200
|
+
let encodings_for_server: &[HeaderValue] = if let Some(token) = preferred {
|
|
201
|
+
// Safe: these are valid header values and the file server only needs to see
|
|
202
|
+
// a minimal representation to pick a cached variant.
|
|
203
|
+
narrowed = [HeaderValue::from_static(token)];
|
|
204
|
+
&narrowed
|
|
205
|
+
} else {
|
|
206
|
+
&[]
|
|
207
|
+
};
|
|
208
|
+
|
|
138
209
|
let response = file_server
|
|
139
210
|
.serve(
|
|
140
211
|
&req,
|
|
@@ -143,7 +214,7 @@ impl MiddlewareLayer for StaticAssets {
|
|
|
143
214
|
serve_range,
|
|
144
215
|
if_modified_since,
|
|
145
216
|
is_head_request,
|
|
146
|
-
|
|
217
|
+
encodings_for_server,
|
|
147
218
|
)
|
|
148
219
|
.await;
|
|
149
220
|
|
|
@@ -156,40 +227,38 @@ impl MiddlewareLayer for StaticAssets {
|
|
|
156
227
|
}
|
|
157
228
|
|
|
158
229
|
fn parse_range_header(headers: &HeaderMap) -> ServeRange {
|
|
159
|
-
let range_header = headers.get(RANGE)
|
|
160
|
-
if range_header.is_none() {
|
|
230
|
+
let Some(range_header) = headers.get(RANGE) else {
|
|
161
231
|
return ServeRange::Full;
|
|
162
|
-
}
|
|
163
|
-
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
let range_header = range_header.to_str().unwrap_or("");
|
|
164
235
|
let bytes_prefix = "bytes=";
|
|
165
236
|
if !range_header.starts_with(bytes_prefix) {
|
|
166
237
|
return ServeRange::Full;
|
|
167
238
|
}
|
|
168
239
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
let range_parts: Vec<&str> = range_str
|
|
240
|
+
// Only consider the first range specifier, ignore multi-range requests.
|
|
241
|
+
let range_str = range_header[bytes_prefix.len()..]
|
|
172
242
|
.split(',')
|
|
173
243
|
.next()
|
|
174
|
-
.unwrap_or("")
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if range_parts.len() != 2 {
|
|
244
|
+
.unwrap_or("");
|
|
245
|
+
|
|
246
|
+
let Some((start_str, end_str)) = range_str.split_once('-') else {
|
|
178
247
|
return ServeRange::Full;
|
|
179
|
-
}
|
|
248
|
+
};
|
|
180
249
|
|
|
181
|
-
let start = if
|
|
182
|
-
|
|
183
|
-
} else if let Ok(start) =
|
|
250
|
+
let start = if start_str.is_empty() {
|
|
251
|
+
end_str.parse::<u64>().unwrap_or(0)
|
|
252
|
+
} else if let Ok(start) = start_str.parse::<u64>() {
|
|
184
253
|
start
|
|
185
254
|
} else {
|
|
186
255
|
return ServeRange::Full;
|
|
187
256
|
};
|
|
188
257
|
|
|
189
|
-
let end = if
|
|
190
|
-
u64::MAX //
|
|
191
|
-
} else if let Ok(end) =
|
|
192
|
-
end
|
|
258
|
+
let end = if end_str.is_empty() {
|
|
259
|
+
u64::MAX // sentinel for open-ended ranges
|
|
260
|
+
} else if let Ok(end) = end_str.parse::<u64>() {
|
|
261
|
+
end
|
|
193
262
|
} else {
|
|
194
263
|
return ServeRange::Full;
|
|
195
264
|
};
|
|
@@ -4,7 +4,7 @@ use argon2::{
|
|
|
4
4
|
};
|
|
5
5
|
|
|
6
6
|
use itsi_error::ItsiError;
|
|
7
|
-
use magnus::{error::Result, Value};
|
|
7
|
+
use magnus::{error::Result, Ruby, Value};
|
|
8
8
|
use serde::Deserialize;
|
|
9
9
|
use serde_magnus::deserialize;
|
|
10
10
|
use sha_crypt::{
|
|
@@ -26,7 +26,13 @@ pub enum HashAlgorithm {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
pub fn create_password_hash(password: String, algo: Value) -> Result<String> {
|
|
29
|
-
let
|
|
29
|
+
let ruby = Ruby::get().map_err(|_| {
|
|
30
|
+
magnus::Error::new(
|
|
31
|
+
magnus::exception::runtime_error(),
|
|
32
|
+
"Failed to acquire Ruby VM handle",
|
|
33
|
+
)
|
|
34
|
+
})?;
|
|
35
|
+
let hash_algorithm: HashAlgorithm = deserialize(&ruby, algo)?;
|
|
30
36
|
match hash_algorithm {
|
|
31
37
|
HashAlgorithm::Bcrypt => {
|
|
32
38
|
// Use the bcrypt crate for password hashing.
|
|
@@ -6,9 +6,11 @@ use redis::{Client, RedisError, Script};
|
|
|
6
6
|
use serde::Deserialize;
|
|
7
7
|
use std::any::Any;
|
|
8
8
|
use std::collections::{HashMap, HashSet};
|
|
9
|
+
use std::fmt::Write as _;
|
|
9
10
|
use std::result::Result;
|
|
11
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
10
12
|
use std::sync::{Arc, LazyLock};
|
|
11
|
-
use std::time::{Duration, Instant};
|
|
13
|
+
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|
12
14
|
use tokio::sync::Mutex as AsyncMutex;
|
|
13
15
|
use tokio::time::timeout;
|
|
14
16
|
use tracing::warn;
|
|
@@ -303,27 +305,37 @@ impl RateLimiter for InMemoryRateLimiter {
|
|
|
303
305
|
|
|
304
306
|
let mut entries = self.entries.write();
|
|
305
307
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
308
|
+
// Avoid per-request allocation: only allocate a String when inserting a new key.
|
|
309
|
+
//
|
|
310
|
+
// NOTE: we use `get_mut` first because `HashMap::entry` for `HashMap<String, _>`
|
|
311
|
+
// requires an owned `String`, which would allocate on every request.
|
|
312
|
+
if let Some(entry) = entries.get_mut(key) {
|
|
313
|
+
if entry.expires_at < now {
|
|
314
|
+
entry.expires_at = now + timeout;
|
|
315
|
+
entry.count = 1;
|
|
316
|
+
} else {
|
|
317
|
+
entry.count += 1;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let ttl = if entry.expires_at > now {
|
|
321
|
+
entry.expires_at.duration_since(now).as_secs()
|
|
322
|
+
} else {
|
|
323
|
+
0
|
|
324
|
+
};
|
|
312
325
|
|
|
313
|
-
|
|
314
|
-
entry.expires_at = now + timeout;
|
|
315
|
-
entry.count = 1;
|
|
316
|
-
} else {
|
|
317
|
-
entry.count += 1;
|
|
326
|
+
return Ok((entry.count, ttl));
|
|
318
327
|
}
|
|
319
328
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
329
|
+
// Insert path: allocate once for the new key.
|
|
330
|
+
entries.insert(
|
|
331
|
+
key.to_owned(),
|
|
332
|
+
RateLimitEntry {
|
|
333
|
+
count: 1,
|
|
334
|
+
expires_at: now + timeout,
|
|
335
|
+
},
|
|
336
|
+
);
|
|
325
337
|
|
|
326
|
-
Ok((
|
|
338
|
+
Ok((1, timeout.as_secs()))
|
|
327
339
|
}
|
|
328
340
|
|
|
329
341
|
async fn check_limit(
|
|
@@ -378,14 +390,49 @@ impl BanManager {
|
|
|
378
390
|
}
|
|
379
391
|
|
|
380
392
|
/// Utility function to create a rate limit key for a specific minute
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
393
|
+
static CACHED_MINUTE_BUCKET_SECS: AtomicU64 = AtomicU64::new(0);
|
|
394
|
+
static CACHED_MINUTE_BUCKET: AtomicU64 = AtomicU64::new(0);
|
|
395
|
+
|
|
396
|
+
#[inline]
|
|
397
|
+
fn cached_minute_bucket() -> u64 {
|
|
398
|
+
// Cache the computed minute bucket and only refresh at most once per second.
|
|
399
|
+
// This avoids a syscall and divisions/mods on every request, while preserving
|
|
400
|
+
// the exact same value as the previous implementation.
|
|
401
|
+
let now_secs = SystemTime::now()
|
|
402
|
+
.duration_since(UNIX_EPOCH)
|
|
403
|
+
.unwrap_or_default()
|
|
404
|
+
.as_secs();
|
|
405
|
+
|
|
406
|
+
let last = CACHED_MINUTE_BUCKET_SECS.load(Ordering::Relaxed);
|
|
407
|
+
if last != now_secs {
|
|
408
|
+
let minutes = (now_secs / 60) % 60;
|
|
409
|
+
CACHED_MINUTE_BUCKET.store(minutes, Ordering::Relaxed);
|
|
410
|
+
CACHED_MINUTE_BUCKET_SECS.store(now_secs, Ordering::Relaxed);
|
|
411
|
+
minutes
|
|
412
|
+
} else {
|
|
413
|
+
CACHED_MINUTE_BUCKET.load(Ordering::Relaxed)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
386
416
|
|
|
387
|
-
|
|
388
|
-
|
|
417
|
+
pub fn create_rate_limit_key(api_key: &str, resource: &str) -> String {
|
|
418
|
+
let minutes = cached_minute_bucket();
|
|
419
|
+
|
|
420
|
+
// Build the exact same string as:
|
|
421
|
+
// format!("ratelimit:{}:{}:{}", api_key, resource, minutes)
|
|
422
|
+
// but avoid `format!` machinery and minimize reallocations.
|
|
423
|
+
let mut s = String::with_capacity(
|
|
424
|
+
"ratelimit:".len() + api_key.len() + 1 + resource.len() + 1 + 2, // minutes is 0-59
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
s.push_str("ratelimit:");
|
|
428
|
+
s.push_str(api_key);
|
|
429
|
+
s.push(':');
|
|
430
|
+
s.push_str(resource);
|
|
431
|
+
s.push(':');
|
|
432
|
+
// u64->decimal without intermediate allocation
|
|
433
|
+
let _ = write!(&mut s, "{}", minutes);
|
|
434
|
+
|
|
435
|
+
s
|
|
389
436
|
}
|
|
390
437
|
|
|
391
438
|
/// Utility function to create a ban key for an IP address
|