itsi 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/.dockerignore +3 -0
- data/CHANGELOG.md +3 -0
- data/Cargo.lock +14 -14
- data/Dockerfile +9 -0
- data/crates/itsi_error/Cargo.toml +1 -1
- data/crates/itsi_rb_helpers/Cargo.toml +2 -2
- data/crates/itsi_rb_helpers/src/heap_value.rs +2 -2
- data/crates/itsi_rb_helpers/src/lib.rs +9 -5
- data/crates/itsi_scheduler/Cargo.toml +2 -2
- data/crates/itsi_server/Cargo.toml +2 -2
- data/crates/itsi_server/src/lib.rs +3 -2
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +34 -18
- data/crates/itsi_server/src/server/frame_stream.rs +2 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +8 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +90 -21
- data/crates/itsi_server/src/services/password_hasher.rs +8 -2
- data/crates/itsi_server/src/services/rate_limiter.rs +72 -25
- data/crates/itsi_server/src/services/static_file_server.rs +35 -9
- data/crates/itsi_tracing/src/lib.rs +3 -3
- data/gems/scheduler/Cargo.lock +10 -10
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/server/Cargo.lock +12 -12
- data/gems/server/lib/itsi/http_request.rb +22 -17
- data/gems/server/lib/itsi/rack_env_pool.rb +7 -17
- data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +8 -1
- data/gems/server/lib/itsi/server/rack_interface.rb +12 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/version.rb +1 -1
- data/mise.toml +2 -0
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4117986c1d041e81b0ffd2aa390c19db3798893d98c30ed9c3fe51d8fb5dafd
|
|
4
|
+
data.tar.gz: b3025a1c7e133a6afedda5cbe45e92f043c36a30aa3d0e62c325040b9a055d06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e27ce25615c7528cc2732c42bbe1966de03b31c91633ddfbbcf0d42bf439bff56c8878bb631dfd3644115309149dfc69e7ac1cf32b059d9f429b9819dd77893
|
|
7
|
+
data.tar.gz: f6f30ce1824850bc24109ac052613f63f43afaf300ee75280d8a2ad24501998cfe2486594f79b5b272bcc53459b823457c103ef32dfa5c59a82e71dab0a1a838
|
data/.dockerignore
ADDED
data/CHANGELOG.md
CHANGED
data/Cargo.lock
CHANGED
|
@@ -1644,7 +1644,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
|
1644
1644
|
|
|
1645
1645
|
[[package]]
|
|
1646
1646
|
name = "itsi-scheduler"
|
|
1647
|
-
version = "0.2.
|
|
1647
|
+
version = "0.2.21-rc1"
|
|
1648
1648
|
dependencies = [
|
|
1649
1649
|
"bytes",
|
|
1650
1650
|
"derive_more",
|
|
@@ -1662,7 +1662,7 @@ dependencies = [
|
|
|
1662
1662
|
|
|
1663
1663
|
[[package]]
|
|
1664
1664
|
name = "itsi-server"
|
|
1665
|
-
version = "0.2.
|
|
1665
|
+
version = "0.2.21-rc1"
|
|
1666
1666
|
dependencies = [
|
|
1667
1667
|
"argon2",
|
|
1668
1668
|
"async-channel",
|
|
@@ -1933,9 +1933,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
|
|
1933
1933
|
|
|
1934
1934
|
[[package]]
|
|
1935
1935
|
name = "magnus"
|
|
1936
|
-
version = "0.
|
|
1936
|
+
version = "0.8.2"
|
|
1937
1937
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1938
|
-
checksum = "
|
|
1938
|
+
checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
|
|
1939
1939
|
dependencies = [
|
|
1940
1940
|
"bytes",
|
|
1941
1941
|
"magnus-macros",
|
|
@@ -1946,9 +1946,9 @@ dependencies = [
|
|
|
1946
1946
|
|
|
1947
1947
|
[[package]]
|
|
1948
1948
|
name = "magnus-macros"
|
|
1949
|
-
version = "0.
|
|
1949
|
+
version = "0.8.0"
|
|
1950
1950
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1951
|
-
checksum = "
|
|
1951
|
+
checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
|
|
1952
1952
|
dependencies = [
|
|
1953
1953
|
"proc-macro2",
|
|
1954
1954
|
"quote",
|
|
@@ -2581,18 +2581,18 @@ dependencies = [
|
|
|
2581
2581
|
|
|
2582
2582
|
[[package]]
|
|
2583
2583
|
name = "rb-sys"
|
|
2584
|
-
version = "0.9.
|
|
2584
|
+
version = "0.9.124"
|
|
2585
2585
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2586
|
-
checksum = "
|
|
2586
|
+
checksum = "c85c4188462601e2aa1469def389c17228566f82ea72f137ed096f21591bc489"
|
|
2587
2587
|
dependencies = [
|
|
2588
2588
|
"rb-sys-build",
|
|
2589
2589
|
]
|
|
2590
2590
|
|
|
2591
2591
|
[[package]]
|
|
2592
2592
|
name = "rb-sys-build"
|
|
2593
|
-
version = "0.9.
|
|
2593
|
+
version = "0.9.124"
|
|
2594
2594
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2595
|
-
checksum = "
|
|
2595
|
+
checksum = "568068db4102230882e6d4ae8de6632e224ca75fe5970f6e026a04e91ed635d3"
|
|
2596
2596
|
dependencies = [
|
|
2597
2597
|
"bindgen",
|
|
2598
2598
|
"lazy_static",
|
|
@@ -2605,9 +2605,9 @@ dependencies = [
|
|
|
2605
2605
|
|
|
2606
2606
|
[[package]]
|
|
2607
2607
|
name = "rb-sys-env"
|
|
2608
|
-
version = "0.
|
|
2608
|
+
version = "0.2.3"
|
|
2609
2609
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2610
|
-
checksum = "
|
|
2610
|
+
checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
|
|
2611
2611
|
|
|
2612
2612
|
[[package]]
|
|
2613
2613
|
name = "rcgen"
|
|
@@ -2999,9 +2999,9 @@ dependencies = [
|
|
|
2999
2999
|
|
|
3000
3000
|
[[package]]
|
|
3001
3001
|
name = "serde_magnus"
|
|
3002
|
-
version = "0.
|
|
3002
|
+
version = "0.11.0"
|
|
3003
3003
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3004
|
-
checksum = "
|
|
3004
|
+
checksum = "8ff64c88ddd26acdcad5a501f18bcc339927b77b69f4a03bfaf2a6fc5ba2ac4b"
|
|
3005
3005
|
dependencies = [
|
|
3006
3006
|
"magnus",
|
|
3007
3007
|
"serde",
|
data/Dockerfile
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
FROM ruby:3.4
|
|
2
|
+
|
|
3
|
+
RUN apt-get update && apt-get install build-essential libclang-dev -y && apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
4
|
+
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
|
5
|
+
|
|
6
|
+
COPY pkg/itsi-server-0.2.21.rc1.gem .
|
|
7
|
+
RUN gem install itsi-server-0.2.21.rc1.gem
|
|
8
|
+
|
|
9
|
+
CMD ["itsi", "serve"]
|
|
@@ -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"
|
|
@@ -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"
|
|
@@ -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
|
|
@@ -127,6 +127,38 @@ impl CacheEntry {
|
|
|
127
127
|
&self,
|
|
128
128
|
supported_encodings: &[HeaderValue],
|
|
129
129
|
) -> (Arc<Bytes>, Option<HeaderValue>) {
|
|
130
|
+
// Fast-path: if the caller already computed a preferred single encoding token,
|
|
131
|
+
// it will pass exactly one HeaderValue (e.g. "br"). This avoids per-request
|
|
132
|
+
// string splitting/parsing on the cached static file hot-path.
|
|
133
|
+
if supported_encodings.len() == 1 {
|
|
134
|
+
let hv = &supported_encodings[0];
|
|
135
|
+
if hv == HEADER_VALUE_ZSTD {
|
|
136
|
+
if let Some(zstd) = self.zstd.as_ref() {
|
|
137
|
+
return (zstd.clone(), Some(HEADER_VALUE_ZSTD.clone()));
|
|
138
|
+
}
|
|
139
|
+
return (self.content.clone(), None);
|
|
140
|
+
}
|
|
141
|
+
if hv == HEADER_VALUE_BR {
|
|
142
|
+
if let Some(br) = self.br.as_ref() {
|
|
143
|
+
return (br.clone(), Some(HEADER_VALUE_BR.clone()));
|
|
144
|
+
}
|
|
145
|
+
return (self.content.clone(), None);
|
|
146
|
+
}
|
|
147
|
+
if hv == HEADER_VALUE_GZIP {
|
|
148
|
+
if let Some(gz) = self.gz.as_ref() {
|
|
149
|
+
return (gz.clone(), Some(HEADER_VALUE_GZIP.clone()));
|
|
150
|
+
}
|
|
151
|
+
return (self.content.clone(), None);
|
|
152
|
+
}
|
|
153
|
+
if hv == HEADER_VALUE_DEFLATE {
|
|
154
|
+
if let Some(deflate) = self.deflate.as_ref() {
|
|
155
|
+
return (deflate.clone(), Some(HEADER_VALUE_DEFLATE.clone()));
|
|
156
|
+
}
|
|
157
|
+
return (self.content.clone(), None);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Slow-path: parse Accept-Encoding values and select the first supported encoding.
|
|
130
162
|
for encoding_header in supported_encodings {
|
|
131
163
|
if let Ok(header_value) = encoding_header.to_str() {
|
|
132
164
|
for header_value in header_value.split(",").map(|hv| hv.trim()) {
|
|
@@ -416,15 +448,12 @@ impl StaticFileServer {
|
|
|
416
448
|
abs_path: &str,
|
|
417
449
|
accept: ResponseFormat,
|
|
418
450
|
) -> std::result::Result<ResolvedAsset, NotFoundBehavior> {
|
|
419
|
-
let ext_opt = Path::new(key)
|
|
420
|
-
.extension()
|
|
421
|
-
.and_then(|e| e.to_str())
|
|
422
|
-
.map(|s| s.to_lowercase());
|
|
451
|
+
let ext_opt = Path::new(key).extension().and_then(|e| e.to_str());
|
|
423
452
|
|
|
424
453
|
// If the allowed list is non-empty, enforce membership
|
|
425
454
|
if !self.allowed_extensions.is_empty() {
|
|
426
455
|
match ext_opt {
|
|
427
|
-
Some(
|
|
456
|
+
Some(ext)
|
|
428
457
|
if self
|
|
429
458
|
.allowed_extensions
|
|
430
459
|
.iter()
|
|
@@ -876,10 +905,7 @@ impl StaticFileServer {
|
|
|
876
905
|
content_length.to_string()
|
|
877
906
|
},
|
|
878
907
|
)
|
|
879
|
-
.header(
|
|
880
|
-
"Last-Modified",
|
|
881
|
-
format_http_date_header(cache_entry.last_modified),
|
|
882
|
-
);
|
|
908
|
+
.header("Last-Modified", cache_entry.last_modified_http_date.clone());
|
|
883
909
|
|
|
884
910
|
if let Some(range) = content_range {
|
|
885
911
|
builder = builder.header("Content-Range", range);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use atty::{
|
|
1
|
+
use atty::{is, Stream};
|
|
2
2
|
use std::{
|
|
3
3
|
env,
|
|
4
4
|
sync::{Mutex, OnceLock},
|
|
@@ -10,8 +10,8 @@ use tracing_subscriber::fmt::{
|
|
|
10
10
|
format::{FmtSpan, JsonFields},
|
|
11
11
|
writer::BoxMakeWriter,
|
|
12
12
|
};
|
|
13
|
-
use tracing_subscriber::{
|
|
14
|
-
use tracing_subscriber::{Layer, Registry
|
|
13
|
+
use tracing_subscriber::{fmt, prelude::*, reload, EnvFilter};
|
|
14
|
+
use tracing_subscriber::{layer::Layered, Layer, Registry};
|
|
15
15
|
|
|
16
16
|
// Global reload handle for changing the level at runtime.
|
|
17
17
|
static RELOAD_HANDLE: OnceLock<
|
data/gems/scheduler/Cargo.lock
CHANGED
|
@@ -318,9 +318,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
|
|
318
318
|
|
|
319
319
|
[[package]]
|
|
320
320
|
name = "magnus"
|
|
321
|
-
version = "0.
|
|
321
|
+
version = "0.8.2"
|
|
322
322
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
323
|
-
checksum = "
|
|
323
|
+
checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
|
|
324
324
|
dependencies = [
|
|
325
325
|
"bytes",
|
|
326
326
|
"magnus-macros",
|
|
@@ -331,9 +331,9 @@ dependencies = [
|
|
|
331
331
|
|
|
332
332
|
[[package]]
|
|
333
333
|
name = "magnus-macros"
|
|
334
|
-
version = "0.
|
|
334
|
+
version = "0.8.0"
|
|
335
335
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
336
|
-
checksum = "
|
|
336
|
+
checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
|
|
337
337
|
dependencies = [
|
|
338
338
|
"proc-macro2",
|
|
339
339
|
"quote",
|
|
@@ -488,18 +488,18 @@ dependencies = [
|
|
|
488
488
|
|
|
489
489
|
[[package]]
|
|
490
490
|
name = "rb-sys"
|
|
491
|
-
version = "0.9.
|
|
491
|
+
version = "0.9.124"
|
|
492
492
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
493
|
-
checksum = "
|
|
493
|
+
checksum = "c85c4188462601e2aa1469def389c17228566f82ea72f137ed096f21591bc489"
|
|
494
494
|
dependencies = [
|
|
495
495
|
"rb-sys-build",
|
|
496
496
|
]
|
|
497
497
|
|
|
498
498
|
[[package]]
|
|
499
499
|
name = "rb-sys-build"
|
|
500
|
-
version = "0.9.
|
|
500
|
+
version = "0.9.124"
|
|
501
501
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
502
|
-
checksum = "
|
|
502
|
+
checksum = "568068db4102230882e6d4ae8de6632e224ca75fe5970f6e026a04e91ed635d3"
|
|
503
503
|
dependencies = [
|
|
504
504
|
"bindgen",
|
|
505
505
|
"lazy_static",
|
|
@@ -512,9 +512,9 @@ dependencies = [
|
|
|
512
512
|
|
|
513
513
|
[[package]]
|
|
514
514
|
name = "rb-sys-env"
|
|
515
|
-
version = "0.
|
|
515
|
+
version = "0.2.3"
|
|
516
516
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
517
|
-
checksum = "
|
|
517
|
+
checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
|
|
518
518
|
|
|
519
519
|
[[package]]
|
|
520
520
|
name = "rcgen"
|
data/gems/server/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",
|
|
@@ -27,6 +27,21 @@ module Itsi
|
|
|
27
27
|
|
|
28
28
|
RACK_HEADER_MAP.default_proc = proc { |_, key| "HTTP_#{key.upcase.gsub(/-/, "_")}" }
|
|
29
29
|
|
|
30
|
+
SPECIAL_RACK_HEADER_MAP = {
|
|
31
|
+
"content-type" => "CONTENT_TYPE",
|
|
32
|
+
"content-length" => "CONTENT_LENGTH",
|
|
33
|
+
"accept" => "HTTP_ACCEPT",
|
|
34
|
+
"accept-encoding" => "HTTP_ACCEPT_ENCODING",
|
|
35
|
+
"accept-language" => "HTTP_ACCEPT_LANGUAGE",
|
|
36
|
+
"user-agent" => "HTTP_USER_AGENT",
|
|
37
|
+
"referer" => "HTTP_REFERER",
|
|
38
|
+
"origin" => "HTTP_ORIGIN",
|
|
39
|
+
"cookie" => "HTTP_COOKIE",
|
|
40
|
+
"authorization" => "HTTP_AUTHORIZATION",
|
|
41
|
+
"x-forwarded-for" => "HTTP_X_FORWARDED_FOR",
|
|
42
|
+
"x-forwarded-proto" => "HTTP_X_FORWARDED_PROTO"
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
30
45
|
HTTP_09 = "HTTP/0.9"
|
|
31
46
|
HTTP_09_ARR = ["HTTP/0.9"].freeze
|
|
32
47
|
HTTP_10 = "HTTP/1.0"
|
|
@@ -63,24 +78,9 @@ module Itsi
|
|
|
63
78
|
end
|
|
64
79
|
env["rack.url_scheme"] = scheme
|
|
65
80
|
env["rack.input"] = build_input_io
|
|
66
|
-
env["rack.hijack"] =
|
|
81
|
+
env["rack.hijack"] = self
|
|
67
82
|
each_header do |k, v|
|
|
68
|
-
env[
|
|
69
|
-
when "content-type" then "CONTENT_TYPE"
|
|
70
|
-
when "content-length" then "CONTENT_LENGTH"
|
|
71
|
-
when "accept" then "HTTP_ACCEPT"
|
|
72
|
-
when "accept-encoding" then "HTTP_ACCEPT_ENCODING"
|
|
73
|
-
when "accept-language" then "HTTP_ACCEPT_LANGUAGE"
|
|
74
|
-
when "user-agent" then "HTTP_USER_AGENT"
|
|
75
|
-
when "referer" then "HTTP_REFERER"
|
|
76
|
-
when "origin" then "HTTP_ORIGIN"
|
|
77
|
-
when "cookie" then "HTTP_COOKIE"
|
|
78
|
-
when "authorization" then "HTTP_AUTHORIZATION"
|
|
79
|
-
when "x-forwarded-for" then "HTTP_X_FORWARDED_FOR"
|
|
80
|
-
when "x-forwarded-proto" then "HTTP_X_FORWARDED_PROTO"
|
|
81
|
-
else RACK_HEADER_MAP[k]
|
|
82
|
-
end
|
|
83
|
-
] = v
|
|
83
|
+
env[SPECIAL_RACK_HEADER_MAP[k] || RACK_HEADER_MAP[k]] = v
|
|
84
84
|
end
|
|
85
85
|
env
|
|
86
86
|
end
|
|
@@ -138,6 +138,11 @@ module Itsi
|
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
+
# Rack expects env["rack.hijack"] to respond to #call.
|
|
142
|
+
def call
|
|
143
|
+
hijack
|
|
144
|
+
end
|
|
145
|
+
|
|
141
146
|
def body
|
|
142
147
|
@body ||= build_input_io
|
|
143
148
|
end
|
|
@@ -32,23 +32,13 @@ module Itsi
|
|
|
32
32
|
|
|
33
33
|
POOL = []
|
|
34
34
|
|
|
35
|
-
def self.checkout
|
|
36
|
-
POOL.pop
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
when "rack.run_once" then recycled[key] = false
|
|
43
|
-
when "rack.multipart.buffer_size" then recycled[key] = 16_384
|
|
44
|
-
when "SCRIPT_NAME", "REQUEST_METHOD", "PATH_INFO", "REQUEST_PATH", "QUERY_STRING", "REMOTE_ADDR",
|
|
45
|
-
"SERVER_PORT", "SERVER_NAME", "SERVER_PROTOCOL", "HTTP_HOST", "HTTP_VERSION", "itsi.request",
|
|
46
|
-
"itsi.response", "rack.version", "rack.url_scheme", "rack.input", "rack.hijack"
|
|
47
|
-
nil
|
|
48
|
-
else recycled.delete(key)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end || RACK_ENV_TEMPLATE.dup
|
|
35
|
+
def self.checkout
|
|
36
|
+
recycled = POOL.pop
|
|
37
|
+
return RACK_ENV_TEMPLATE.dup unless recycled
|
|
38
|
+
|
|
39
|
+
# Reset in C rather than iterating key-by-key in Ruby for every request.
|
|
40
|
+
recycled.replace(RACK_ENV_TEMPLATE)
|
|
41
|
+
recycled
|
|
52
42
|
end
|
|
53
43
|
|
|
54
44
|
def self.checkin(env)
|
|
@@ -33,9 +33,16 @@ module Itsi
|
|
|
33
33
|
}
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
RedirectTarget = TypedStruct.new do
|
|
37
|
+
{
|
|
38
|
+
to: (Required() & Type(String)),
|
|
39
|
+
type: Enum(["permanent", "temporary", "found", "moved_permanently"]).default("moved_permanently")
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
36
43
|
RedirectResponse = TypedStruct.new do
|
|
37
44
|
{
|
|
38
|
-
redirect: Type(
|
|
45
|
+
redirect: Type(RedirectTarget) & Required()
|
|
39
46
|
}
|
|
40
47
|
end
|
|
41
48
|
|
|
@@ -62,6 +62,18 @@ module Itsi
|
|
|
62
62
|
# stream this response.
|
|
63
63
|
body_streamer.call(response)
|
|
64
64
|
|
|
65
|
+
elsif body.is_a?(Array)
|
|
66
|
+
if body.length == 1
|
|
67
|
+
response.send_and_close(body[0].to_s)
|
|
68
|
+
else
|
|
69
|
+
buffer = nil
|
|
70
|
+
body.each do |part|
|
|
71
|
+
response << buffer.to_s if buffer
|
|
72
|
+
buffer = part
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
response.send_and_close(buffer.to_s)
|
|
76
|
+
end
|
|
65
77
|
elsif body.respond_to?(:each) || body.respond_to?(:to_ary)
|
|
66
78
|
# If we're enumerable with more than one chunk
|
|
67
79
|
# also stream, otherwise write in a single chunk
|
data/lib/itsi/version.rb
CHANGED
data/mise.toml
ADDED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: itsi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.21.
|
|
4
|
+
version: 0.2.21.rc2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wouter Coppieters
|
|
@@ -15,28 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.2.21.
|
|
18
|
+
version: 0.2.21.rc2
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.2.21.
|
|
25
|
+
version: 0.2.21.rc2
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: itsi-server
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.2.21.
|
|
32
|
+
version: 0.2.21.rc2
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - '='
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.2.21.
|
|
39
|
+
version: 0.2.21.rc2
|
|
40
40
|
description: Wrapper Gem for both the Itsi server and the Itsi Fiber scheduler
|
|
41
41
|
email:
|
|
42
42
|
- wc@pico.net.nz
|
|
@@ -44,10 +44,12 @@ executables: []
|
|
|
44
44
|
extensions: []
|
|
45
45
|
extra_rdoc_files: []
|
|
46
46
|
files:
|
|
47
|
+
- ".dockerignore"
|
|
47
48
|
- ".zed/settings.json"
|
|
48
49
|
- CHANGELOG.md
|
|
49
50
|
- Cargo.lock
|
|
50
51
|
- Cargo.toml
|
|
52
|
+
- Dockerfile
|
|
51
53
|
- LICENSE.txt
|
|
52
54
|
- README.md
|
|
53
55
|
- Rakefile
|
|
@@ -1119,6 +1121,7 @@ files:
|
|
|
1119
1121
|
- itsi-server-100.png
|
|
1120
1122
|
- lib/itsi.rb
|
|
1121
1123
|
- lib/itsi/version.rb
|
|
1124
|
+
- mise.toml
|
|
1122
1125
|
homepage: https://itsi.fyi
|
|
1123
1126
|
licenses:
|
|
1124
1127
|
- LGPL-3.0
|