itsi-scheduler 0.2.2 → 0.2.4
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 +74 -17
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_server/Cargo.toml +1 -1
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +26 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +28 -11
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +1 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +14 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +86 -41
- data/ext/itsi_server/src/services/itsi_http_service.rs +46 -35
- data/ext/itsi_server/src/services/static_file_server.rs +31 -3
- data/lib/itsi/scheduler/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c574c681706b2c5e414d9a976c82cc56bc979e5cec2e24f76bd4f4696e84632
|
4
|
+
data.tar.gz: 14857bd2404e1b8cc5d0de66781d553b3c88d74d010b1d4300b7e5ff84e0bd53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9678cef317ffd880ea2d6b7acf46819dca15003fc025b4b971812f14fee2afdd3dff073ae3ada55ebd70a7a11bb05a62e6e7e9d1ff70485fa4f20bf06093a6d0
|
7
|
+
data.tar.gz: 3a433e4c5177d54a00c05d05456ce74b7f4aa2c1bb6261bf9873d63d8b97180a7edc2a121d38642c04642ca3550a863927f9e2ba08c3c4c00aba1dd1837feaf9
|
data/Cargo.lock
CHANGED
@@ -13,9 +13,9 @@ dependencies = [
|
|
13
13
|
|
14
14
|
[[package]]
|
15
15
|
name = "anyhow"
|
16
|
-
version = "1.0.
|
16
|
+
version = "1.0.98"
|
17
17
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
18
|
-
checksum = "
|
18
|
+
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
19
19
|
|
20
20
|
[[package]]
|
21
21
|
name = "atty"
|
@@ -213,7 +213,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
213
213
|
|
214
214
|
[[package]]
|
215
215
|
name = "itsi-scheduler"
|
216
|
-
version = "0.2.
|
216
|
+
version = "0.2.4"
|
217
217
|
dependencies = [
|
218
218
|
"bytes",
|
219
219
|
"derive_more",
|
@@ -286,9 +286,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|
286
286
|
|
287
287
|
[[package]]
|
288
288
|
name = "libc"
|
289
|
-
version = "0.2.
|
289
|
+
version = "0.2.172"
|
290
290
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
291
|
-
checksum = "
|
291
|
+
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
292
292
|
|
293
293
|
[[package]]
|
294
294
|
name = "libloading"
|
@@ -297,7 +297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
297
297
|
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
298
298
|
dependencies = [
|
299
299
|
"cfg-if",
|
300
|
-
"windows-targets",
|
300
|
+
"windows-targets 0.48.5",
|
301
301
|
]
|
302
302
|
|
303
303
|
[[package]]
|
@@ -443,7 +443,7 @@ dependencies = [
|
|
443
443
|
"libc",
|
444
444
|
"redox_syscall",
|
445
445
|
"smallvec",
|
446
|
-
"windows-targets",
|
446
|
+
"windows-targets 0.52.6",
|
447
447
|
]
|
448
448
|
|
449
449
|
[[package]]
|
@@ -470,9 +470,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
|
470
470
|
|
471
471
|
[[package]]
|
472
472
|
name = "proc-macro2"
|
473
|
-
version = "1.0.
|
473
|
+
version = "1.0.95"
|
474
474
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
475
|
-
checksum = "
|
475
|
+
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
476
476
|
dependencies = [
|
477
477
|
"unicode-ident",
|
478
478
|
]
|
@@ -932,7 +932,22 @@ version = "0.52.0"
|
|
932
932
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
933
933
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
934
934
|
dependencies = [
|
935
|
-
"windows-targets",
|
935
|
+
"windows-targets 0.52.6",
|
936
|
+
]
|
937
|
+
|
938
|
+
[[package]]
|
939
|
+
name = "windows-targets"
|
940
|
+
version = "0.48.5"
|
941
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
942
|
+
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
943
|
+
dependencies = [
|
944
|
+
"windows_aarch64_gnullvm 0.48.5",
|
945
|
+
"windows_aarch64_msvc 0.48.5",
|
946
|
+
"windows_i686_gnu 0.48.5",
|
947
|
+
"windows_i686_msvc 0.48.5",
|
948
|
+
"windows_x86_64_gnu 0.48.5",
|
949
|
+
"windows_x86_64_gnullvm 0.48.5",
|
950
|
+
"windows_x86_64_msvc 0.48.5",
|
936
951
|
]
|
937
952
|
|
938
953
|
[[package]]
|
@@ -941,28 +956,46 @@ version = "0.52.6"
|
|
941
956
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
942
957
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
943
958
|
dependencies = [
|
944
|
-
"windows_aarch64_gnullvm",
|
945
|
-
"windows_aarch64_msvc",
|
946
|
-
"windows_i686_gnu",
|
959
|
+
"windows_aarch64_gnullvm 0.52.6",
|
960
|
+
"windows_aarch64_msvc 0.52.6",
|
961
|
+
"windows_i686_gnu 0.52.6",
|
947
962
|
"windows_i686_gnullvm",
|
948
|
-
"windows_i686_msvc",
|
949
|
-
"windows_x86_64_gnu",
|
950
|
-
"windows_x86_64_gnullvm",
|
951
|
-
"windows_x86_64_msvc",
|
963
|
+
"windows_i686_msvc 0.52.6",
|
964
|
+
"windows_x86_64_gnu 0.52.6",
|
965
|
+
"windows_x86_64_gnullvm 0.52.6",
|
966
|
+
"windows_x86_64_msvc 0.52.6",
|
952
967
|
]
|
953
968
|
|
969
|
+
[[package]]
|
970
|
+
name = "windows_aarch64_gnullvm"
|
971
|
+
version = "0.48.5"
|
972
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
973
|
+
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
974
|
+
|
954
975
|
[[package]]
|
955
976
|
name = "windows_aarch64_gnullvm"
|
956
977
|
version = "0.52.6"
|
957
978
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
958
979
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
959
980
|
|
981
|
+
[[package]]
|
982
|
+
name = "windows_aarch64_msvc"
|
983
|
+
version = "0.48.5"
|
984
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
985
|
+
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
986
|
+
|
960
987
|
[[package]]
|
961
988
|
name = "windows_aarch64_msvc"
|
962
989
|
version = "0.52.6"
|
963
990
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
964
991
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
965
992
|
|
993
|
+
[[package]]
|
994
|
+
name = "windows_i686_gnu"
|
995
|
+
version = "0.48.5"
|
996
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
997
|
+
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
998
|
+
|
966
999
|
[[package]]
|
967
1000
|
name = "windows_i686_gnu"
|
968
1001
|
version = "0.52.6"
|
@@ -975,24 +1008,48 @@ version = "0.52.6"
|
|
975
1008
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
976
1009
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
977
1010
|
|
1011
|
+
[[package]]
|
1012
|
+
name = "windows_i686_msvc"
|
1013
|
+
version = "0.48.5"
|
1014
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
1015
|
+
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
1016
|
+
|
978
1017
|
[[package]]
|
979
1018
|
name = "windows_i686_msvc"
|
980
1019
|
version = "0.52.6"
|
981
1020
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
982
1021
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
983
1022
|
|
1023
|
+
[[package]]
|
1024
|
+
name = "windows_x86_64_gnu"
|
1025
|
+
version = "0.48.5"
|
1026
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
1027
|
+
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
1028
|
+
|
984
1029
|
[[package]]
|
985
1030
|
name = "windows_x86_64_gnu"
|
986
1031
|
version = "0.52.6"
|
987
1032
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
988
1033
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
989
1034
|
|
1035
|
+
[[package]]
|
1036
|
+
name = "windows_x86_64_gnullvm"
|
1037
|
+
version = "0.48.5"
|
1038
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
1039
|
+
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
1040
|
+
|
990
1041
|
[[package]]
|
991
1042
|
name = "windows_x86_64_gnullvm"
|
992
1043
|
version = "0.52.6"
|
993
1044
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
994
1045
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
995
1046
|
|
1047
|
+
[[package]]
|
1048
|
+
name = "windows_x86_64_msvc"
|
1049
|
+
version = "0.48.5"
|
1050
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
1051
|
+
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
1052
|
+
|
996
1053
|
[[package]]
|
997
1054
|
name = "windows_x86_64_msvc"
|
998
1055
|
version = "0.52.6"
|
data/ext/itsi_server/Cargo.toml
CHANGED
@@ -14,7 +14,7 @@ use magnus::{
|
|
14
14
|
block::Proc,
|
15
15
|
error::Result,
|
16
16
|
value::{LazyId, ReprValue},
|
17
|
-
RArray, RHash, Ruby, Symbol, Value,
|
17
|
+
RArray, RHash, Ruby, Symbol, TryConvert, Value,
|
18
18
|
};
|
19
19
|
use nix::{
|
20
20
|
fcntl::{fcntl, FcntlArg, FdFlag},
|
@@ -124,9 +124,17 @@ impl ServerParams {
|
|
124
124
|
debug!("Loading Itsi Scheduler");
|
125
125
|
ruby.require("itsi/scheduler")?;
|
126
126
|
}
|
127
|
-
let
|
127
|
+
let result_pair = self
|
128
128
|
.middleware_loader
|
129
|
-
.call::<
|
129
|
+
.call::<(), RArray>(())
|
130
|
+
.inspect_err(|e| {
|
131
|
+
eprintln!("Error loading middleware: {:?}", e);
|
132
|
+
if let Some(err_value) = e.value() {
|
133
|
+
print_rb_backtrace(err_value);
|
134
|
+
}
|
135
|
+
})?;
|
136
|
+
let routes_raw = result_pair
|
137
|
+
.entry::<Option<Value>>(0)
|
130
138
|
.inspect_err(|e| {
|
131
139
|
eprintln!("Error loading middleware: {:?}", e);
|
132
140
|
if let Some(err_value) = e.value() {
|
@@ -134,6 +142,21 @@ impl ServerParams {
|
|
134
142
|
}
|
135
143
|
})?
|
136
144
|
.map(|mw| mw.into());
|
145
|
+
let error_lines = result_pair.entry::<Option<RArray>>(1).inspect_err(|e| {
|
146
|
+
eprintln!("Error loading middleware: {:?}", e);
|
147
|
+
if let Some(err_value) = e.value() {
|
148
|
+
print_rb_backtrace(err_value);
|
149
|
+
}
|
150
|
+
})?;
|
151
|
+
if error_lines.is_some_and(|r| !r.is_empty()) {
|
152
|
+
let errors: Vec<String> =
|
153
|
+
Vec::<String>::try_convert(error_lines.unwrap().as_value())?;
|
154
|
+
ItsiServerConfig::print_config_errors(errors);
|
155
|
+
return Err(magnus::Error::new(
|
156
|
+
magnus::exception::runtime_error(),
|
157
|
+
"Failed to set middleware",
|
158
|
+
));
|
159
|
+
}
|
137
160
|
let middleware = MiddlewareSet::new(routes_raw)?;
|
138
161
|
self.middleware.set(middleware).map_err(|_| {
|
139
162
|
magnus::Error::new(
|
@@ -1,5 +1,6 @@
|
|
1
1
|
use crate::{
|
2
|
-
server::http_message_types::
|
2
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
3
|
+
services::itsi_http_service::HttpRequestContext,
|
3
4
|
};
|
4
5
|
|
5
6
|
use super::{
|
@@ -13,6 +14,7 @@ use async_compression::{
|
|
13
14
|
};
|
14
15
|
use async_trait::async_trait;
|
15
16
|
use bytes::{Bytes, BytesMut};
|
17
|
+
use either::Either;
|
16
18
|
use futures::TryStreamExt;
|
17
19
|
use http::{
|
18
20
|
header::{GetAll, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE},
|
@@ -20,6 +22,7 @@ use http::{
|
|
20
22
|
};
|
21
23
|
use http_body_util::{combinators::BoxBody, BodyExt, Full, StreamBody};
|
22
24
|
use hyper::body::{Body, Frame};
|
25
|
+
use magnus::error::Result;
|
23
26
|
use serde::{Deserialize, Serialize};
|
24
27
|
use std::convert::Infallible;
|
25
28
|
use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
|
@@ -151,6 +154,15 @@ fn update_content_encoding(parts: &mut http::response::Parts, new_encoding: Head
|
|
151
154
|
|
152
155
|
#[async_trait]
|
153
156
|
impl MiddlewareLayer for Compression {
|
157
|
+
async fn before(
|
158
|
+
&self,
|
159
|
+
req: HttpRequest,
|
160
|
+
context: &mut HttpRequestContext,
|
161
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
162
|
+
context.set_supported_encoding_set(&req);
|
163
|
+
Ok(Either::Left(req))
|
164
|
+
}
|
165
|
+
|
154
166
|
/// We'll apply compression on the response, where appropriate.
|
155
167
|
/// This is if:
|
156
168
|
/// * The response body is larger than the minimum size.
|
@@ -207,16 +219,21 @@ impl MiddlewareLayer for Compression {
|
|
207
219
|
}
|
208
220
|
}
|
209
221
|
|
210
|
-
let compression_method =
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
222
|
+
let compression_method =
|
223
|
+
if let Some(supported_encoding_set) = context.supported_encoding_set() {
|
224
|
+
match find_first_supported(
|
225
|
+
supported_encoding_set,
|
226
|
+
self.algorithms.iter().map(|algo| algo.as_str()),
|
227
|
+
) {
|
228
|
+
Some("gzip") => CompressionAlgorithm::Gzip,
|
229
|
+
Some("br") => CompressionAlgorithm::Brotli,
|
230
|
+
Some("deflate") => CompressionAlgorithm::Deflate,
|
231
|
+
Some("zstd") => CompressionAlgorithm::Zstd,
|
232
|
+
_ => CompressionAlgorithm::Identity,
|
233
|
+
}
|
234
|
+
} else {
|
235
|
+
CompressionAlgorithm::Identity
|
236
|
+
};
|
220
237
|
|
221
238
|
debug!(
|
222
239
|
target: "middleware::compress",
|
@@ -62,7 +62,7 @@ impl MiddlewareLayer for LogRequests {
|
|
62
62
|
req: HttpRequest,
|
63
63
|
context: &mut HttpRequestContext,
|
64
64
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
65
|
-
context.
|
65
|
+
context.init_logging_params();
|
66
66
|
if let Some(LogConfig { level, format }) = self.before.as_ref() {
|
67
67
|
level.log(format.rewrite_request(&req, context));
|
68
68
|
}
|
@@ -33,7 +33,7 @@ use reqwest::{
|
|
33
33
|
Body, Client, Url,
|
34
34
|
};
|
35
35
|
use serde::Deserialize;
|
36
|
-
use tracing::
|
36
|
+
use tracing::debug;
|
37
37
|
|
38
38
|
#[derive(Debug, Clone, Deserialize)]
|
39
39
|
pub struct Proxy {
|
@@ -324,7 +324,6 @@ impl MiddlewareLayer for Proxy {
|
|
324
324
|
.unwrap_or("")
|
325
325
|
});
|
326
326
|
|
327
|
-
info!("Extracted host str is {}", host_str);
|
328
327
|
let req_info = RequestInfo {
|
329
328
|
method: req.method().clone(),
|
330
329
|
headers: req_headers.clone(),
|
@@ -12,10 +12,11 @@ use async_trait::async_trait;
|
|
12
12
|
use either::Either;
|
13
13
|
use http::{
|
14
14
|
header::{IF_MODIFIED_SINCE, RANGE},
|
15
|
-
HeaderMap, Method,
|
15
|
+
HeaderMap, HeaderValue, Method,
|
16
16
|
};
|
17
17
|
use itsi_error::ItsiError;
|
18
18
|
use magnus::error::Result;
|
19
|
+
use moka::sync::Cache;
|
19
20
|
use regex::Regex;
|
20
21
|
use serde::Deserialize;
|
21
22
|
use std::{collections::HashMap, path::PathBuf, sync::OnceLock, time::Duration};
|
@@ -75,6 +76,10 @@ impl MiddlewareLayer for StaticAssets {
|
|
75
76
|
recheck_interval: Duration::from_secs(self.file_check_interval),
|
76
77
|
serve_hidden_files: self.serve_hidden_files,
|
77
78
|
allowed_extensions: self.allowed_extensions.clone(),
|
79
|
+
miss_cache: Cache::builder()
|
80
|
+
.max_capacity(self.max_files_in_memory)
|
81
|
+
.time_to_live(Duration::from_secs(self.file_check_interval))
|
82
|
+
.build(),
|
78
83
|
})?)
|
79
84
|
.map_err(ItsiError::new)?;
|
80
85
|
Ok(())
|
@@ -90,6 +95,8 @@ impl MiddlewareLayer for StaticAssets {
|
|
90
95
|
debug!(target: "middleware::static_assets", "Refusing to handle non-GET/HEAD request");
|
91
96
|
return Ok(Either::Left(req));
|
92
97
|
}
|
98
|
+
|
99
|
+
context.set_supported_encoding_set(&req);
|
93
100
|
let abs_path = req.uri().path();
|
94
101
|
let rel_path = if !self.relative_path {
|
95
102
|
abs_path.trim_start_matches("/")
|
@@ -123,6 +130,10 @@ impl MiddlewareLayer for StaticAssets {
|
|
123
130
|
|
124
131
|
// Let the file server handle everything
|
125
132
|
let file_server = self.file_server.get().unwrap();
|
133
|
+
let encodings: &[HeaderValue] = context
|
134
|
+
.supported_encoding_set()
|
135
|
+
.map(Vec::as_slice)
|
136
|
+
.unwrap_or(&[] as &[HeaderValue]);
|
126
137
|
let response = file_server
|
127
138
|
.serve(
|
128
139
|
&req,
|
@@ -131,9 +142,10 @@ impl MiddlewareLayer for StaticAssets {
|
|
131
142
|
serve_range,
|
132
143
|
if_modified_since,
|
133
144
|
is_head_request,
|
134
|
-
|
145
|
+
encodings,
|
135
146
|
)
|
136
147
|
.await;
|
148
|
+
|
137
149
|
if response.is_none() {
|
138
150
|
Ok(Either::Left(req))
|
139
151
|
} else {
|
@@ -50,6 +50,35 @@ pub fn parse_template(template: &str) -> Vec<Segment> {
|
|
50
50
|
}
|
51
51
|
|
52
52
|
impl StringRewrite {
|
53
|
+
/// Apply a single modifier of the form `op:arg` (or for replace `op:from,to`)
|
54
|
+
#[inline]
|
55
|
+
fn apply_modifier(s: &mut String, mod_str: &str) {
|
56
|
+
if let Some((op, arg)) = mod_str.split_once(':') {
|
57
|
+
match op {
|
58
|
+
"strip_prefix" => {
|
59
|
+
if s.starts_with(arg) {
|
60
|
+
let _ = s.drain(..arg.len());
|
61
|
+
}
|
62
|
+
}
|
63
|
+
"strip_suffix" => {
|
64
|
+
if s.ends_with(arg) {
|
65
|
+
let len = s.len();
|
66
|
+
let start = len.saturating_sub(arg.len());
|
67
|
+
let _ = s.drain(start..);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
"replace" => {
|
71
|
+
if let Some((from, to)) = arg.split_once(',') {
|
72
|
+
if s.contains(from) {
|
73
|
+
*s = s.replace(from, to);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
_ => {}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
53
82
|
pub fn rewrite_request(&self, req: &HttpRequest, context: &HttpRequestContext) -> String {
|
54
83
|
let segments = self
|
55
84
|
.segments
|
@@ -63,9 +92,17 @@ impl StringRewrite {
|
|
63
92
|
|
64
93
|
for segment in segments {
|
65
94
|
match segment {
|
66
|
-
Segment::Literal(text) =>
|
67
|
-
|
68
|
-
|
95
|
+
Segment::Literal(text) => {
|
96
|
+
result.push_str(text);
|
97
|
+
}
|
98
|
+
Segment::Placeholder(raw) => {
|
99
|
+
// split into key and optional modifier
|
100
|
+
let mut parts = raw.split('|');
|
101
|
+
let key = parts.next().unwrap();
|
102
|
+
let modifiers = parts; // zero o
|
103
|
+
|
104
|
+
// 1) lookup the raw replacement
|
105
|
+
let mut replacement = match key {
|
69
106
|
"request_id" => context.short_request_id(),
|
70
107
|
"request_id_full" => context.request_id(),
|
71
108
|
"method" => req.method().as_str().to_string(),
|
@@ -76,13 +113,13 @@ impl StringRewrite {
|
|
76
113
|
.uri()
|
77
114
|
.path_and_query()
|
78
115
|
.map(|pq| pq.to_string())
|
79
|
-
.
|
116
|
+
.unwrap_or_default(),
|
80
117
|
"query" => {
|
81
|
-
let
|
82
|
-
if
|
83
|
-
|
118
|
+
let q = req.uri().query().unwrap_or("");
|
119
|
+
if q.is_empty() {
|
120
|
+
"".to_string()
|
84
121
|
} else {
|
85
|
-
format!("?{}",
|
122
|
+
format!("?{}", q)
|
86
123
|
}
|
87
124
|
}
|
88
125
|
"port" => req
|
@@ -91,31 +128,34 @@ impl StringRewrite {
|
|
91
128
|
.map(|p| p.to_string())
|
92
129
|
.unwrap_or_else(|| "80".to_string()),
|
93
130
|
"start_time" => {
|
94
|
-
if let Some(
|
95
|
-
|
131
|
+
if let Some(ts) = context.start_time() {
|
132
|
+
ts.format("%Y-%m-%d:%H:%M:%S:%3f").to_string()
|
96
133
|
} else {
|
97
134
|
"N/A".to_string()
|
98
135
|
}
|
99
136
|
}
|
100
137
|
other => {
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
}
|
114
|
-
} else {
|
138
|
+
// headers first
|
139
|
+
if let Some(hv) = req.headers().get(other) {
|
140
|
+
hv.to_str().unwrap_or("").to_string()
|
141
|
+
}
|
142
|
+
// then any regex‐capture
|
143
|
+
else if let Some(caps) = &captures {
|
144
|
+
caps.name(other)
|
145
|
+
.map(|m| m.as_str().to_string())
|
146
|
+
.unwrap_or_else(|| format!("{{{}}}", other))
|
147
|
+
}
|
148
|
+
// fallback: leave placeholder intact
|
149
|
+
else {
|
115
150
|
format!("{{{}}}", other)
|
116
151
|
}
|
117
152
|
}
|
118
153
|
};
|
154
|
+
|
155
|
+
for m in modifiers {
|
156
|
+
Self::apply_modifier(&mut replacement, m);
|
157
|
+
}
|
158
|
+
|
119
159
|
result.push_str(&replacement);
|
120
160
|
}
|
121
161
|
}
|
@@ -132,37 +172,42 @@ impl StringRewrite {
|
|
132
172
|
let mut result = String::with_capacity(self.template_string.len());
|
133
173
|
for segment in segments {
|
134
174
|
match segment {
|
135
|
-
Segment::Literal(text) =>
|
136
|
-
|
137
|
-
|
175
|
+
Segment::Literal(text) => {
|
176
|
+
result.push_str(text);
|
177
|
+
}
|
178
|
+
Segment::Placeholder(raw) => {
|
179
|
+
let mut parts = raw.split('|');
|
180
|
+
let key = parts.next().unwrap();
|
181
|
+
let modifiers = parts; // zero o
|
182
|
+
|
183
|
+
let mut replacement = match key {
|
138
184
|
"request_id" => context.short_request_id(),
|
139
185
|
"request_id_full" => context.request_id(),
|
140
186
|
"status" => resp.status().as_str().to_string(),
|
141
187
|
"addr" => context.addr.to_owned(),
|
142
188
|
"response_time" => {
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
}
|
147
|
-
format!("{}ms", response_time.num_milliseconds())
|
148
|
-
}
|
189
|
+
let dur = context.get_response_time();
|
190
|
+
let micros = dur.as_micros();
|
191
|
+
if micros < 1_000 {
|
192
|
+
format!("{}µs", micros)
|
149
193
|
} else {
|
150
|
-
|
194
|
+
let ms = dur.as_secs_f64() * 1_000.0;
|
195
|
+
format!("{:.3}ms", ms)
|
151
196
|
}
|
152
197
|
}
|
153
198
|
other => {
|
154
|
-
|
155
|
-
|
156
|
-
if let Ok(s) = val.to_str() {
|
157
|
-
s.to_string()
|
158
|
-
} else {
|
159
|
-
"".to_string()
|
160
|
-
}
|
199
|
+
if let Some(hv) = resp.headers().get(other) {
|
200
|
+
hv.to_str().unwrap_or("").to_string()
|
161
201
|
} else {
|
162
202
|
format!("{{{}}}", other)
|
163
203
|
}
|
164
204
|
}
|
165
205
|
};
|
206
|
+
|
207
|
+
for m in modifiers {
|
208
|
+
Self::apply_modifier(&mut replacement, m);
|
209
|
+
}
|
210
|
+
|
166
211
|
result.push_str(&replacement);
|
167
212
|
}
|
168
213
|
}
|
@@ -1,14 +1,15 @@
|
|
1
1
|
use crate::default_responses::{NOT_FOUND_RESPONSE, TIMEOUT_RESPONSE};
|
2
2
|
use crate::ruby_types::itsi_server::itsi_server_config::{ItsiServerTokenPreference, ServerParams};
|
3
3
|
use crate::server::binds::listener::ListenerInfo;
|
4
|
-
use crate::server::http_message_types::{
|
4
|
+
use crate::server::http_message_types::{
|
5
|
+
ConversionExt, HttpRequest, HttpResponse, RequestExt, ResponseFormat,
|
6
|
+
};
|
5
7
|
use crate::server::lifecycle_event::LifecycleEvent;
|
6
8
|
use crate::server::middleware_stack::MiddlewareLayer;
|
7
9
|
use crate::server::request_job::RequestJob;
|
8
10
|
use crate::server::serve_strategy::single_mode::RunningPhase;
|
9
11
|
use crate::server::signal::send_lifecycle_event;
|
10
|
-
use chrono;
|
11
|
-
use chrono::Local;
|
12
|
+
use chrono::{self, DateTime, Local};
|
12
13
|
use either::Either;
|
13
14
|
use http::header::ACCEPT_ENCODING;
|
14
15
|
use http::{HeaderValue, Request};
|
@@ -18,6 +19,7 @@ use itsi_error::ItsiError;
|
|
18
19
|
use regex::Regex;
|
19
20
|
use std::sync::atomic::{AtomicBool, Ordering};
|
20
21
|
use std::sync::OnceLock;
|
22
|
+
use std::time::{Duration, Instant};
|
21
23
|
use tracing::error;
|
22
24
|
|
23
25
|
use std::{future::Future, ops::Deref, pin::Pin, sync::Arc};
|
@@ -68,17 +70,16 @@ impl Deref for RequestContextInner {
|
|
68
70
|
}
|
69
71
|
|
70
72
|
pub struct RequestContextInner {
|
71
|
-
pub request_id:
|
73
|
+
pub request_id: u64,
|
72
74
|
pub service: ItsiHttpService,
|
73
75
|
pub accept: ResponseFormat,
|
74
76
|
pub matching_pattern: Option<Arc<Regex>>,
|
75
77
|
pub origin: OnceLock<Option<String>>,
|
76
78
|
pub response_format: OnceLock<ResponseFormat>,
|
77
|
-
pub
|
78
|
-
pub
|
79
|
-
pub request_start_time: OnceLock<chrono::DateTime<Local>>,
|
79
|
+
pub request_start_time: OnceLock<DateTime<Local>>,
|
80
|
+
pub start_instant: Instant,
|
80
81
|
pub if_none_match: OnceLock<Option<String>>,
|
81
|
-
pub supported_encoding_set: Vec<HeaderValue
|
82
|
+
pub supported_encoding_set: OnceLock<Vec<HeaderValue>>,
|
82
83
|
pub is_ruby_request: Arc<AtomicBool>,
|
83
84
|
}
|
84
85
|
|
@@ -87,27 +88,38 @@ impl HttpRequestContext {
|
|
87
88
|
service: ItsiHttpService,
|
88
89
|
matching_pattern: Option<Arc<Regex>>,
|
89
90
|
accept: ResponseFormat,
|
90
|
-
supported_encoding_set: Vec<HeaderValue>,
|
91
91
|
is_ruby_request: Arc<AtomicBool>,
|
92
92
|
) -> Self {
|
93
93
|
HttpRequestContext {
|
94
94
|
inner: Arc::new(RequestContextInner {
|
95
|
-
request_id: rand::random::<
|
95
|
+
request_id: rand::random::<u64>(),
|
96
96
|
service,
|
97
97
|
matching_pattern,
|
98
98
|
accept,
|
99
99
|
origin: OnceLock::new(),
|
100
100
|
response_format: OnceLock::new(),
|
101
|
-
start_time: chrono::Utc::now(),
|
102
|
-
request: None,
|
103
101
|
request_start_time: OnceLock::new(),
|
102
|
+
start_instant: Instant::now(),
|
104
103
|
if_none_match: OnceLock::new(),
|
105
|
-
supported_encoding_set,
|
104
|
+
supported_encoding_set: OnceLock::new(),
|
106
105
|
is_ruby_request,
|
107
106
|
}),
|
108
107
|
}
|
109
108
|
}
|
110
109
|
|
110
|
+
pub fn set_supported_encoding_set(&self, req: &HttpRequest) {
|
111
|
+
let supported_encoding_set = req
|
112
|
+
.headers()
|
113
|
+
.get_all(ACCEPT_ENCODING)
|
114
|
+
.into_iter()
|
115
|
+
.cloned()
|
116
|
+
.collect::<Vec<_>>();
|
117
|
+
self.inner
|
118
|
+
.supported_encoding_set
|
119
|
+
.set(supported_encoding_set)
|
120
|
+
.unwrap();
|
121
|
+
}
|
122
|
+
|
111
123
|
pub fn set_origin(&self, origin: Option<String>) {
|
112
124
|
self.inner.origin.set(origin).unwrap();
|
113
125
|
}
|
@@ -121,28 +133,29 @@ impl HttpRequestContext {
|
|
121
133
|
}
|
122
134
|
|
123
135
|
pub fn short_request_id(&self) -> String {
|
124
|
-
format!("{:
|
136
|
+
format!("{:08x}", self.inner.request_id & 0xffff_ffff)
|
125
137
|
}
|
126
138
|
|
127
139
|
pub fn request_id(&self) -> String {
|
128
|
-
format!("{:
|
140
|
+
format!("{:08x}", self.inner.request_id)
|
129
141
|
}
|
130
142
|
|
131
|
-
pub fn
|
143
|
+
pub fn init_logging_params(&self) {
|
132
144
|
self.inner
|
133
145
|
.request_start_time
|
134
146
|
.get_or_init(chrono::Local::now);
|
135
147
|
}
|
136
148
|
|
137
|
-
pub fn
|
149
|
+
pub fn start_instant(&self) -> Instant {
|
150
|
+
self.inner.start_instant
|
151
|
+
}
|
152
|
+
|
153
|
+
pub fn start_time(&self) -> Option<DateTime<Local>> {
|
138
154
|
self.inner.request_start_time.get().cloned()
|
139
155
|
}
|
140
156
|
|
141
|
-
pub fn get_response_time(&self) ->
|
142
|
-
self.inner
|
143
|
-
.request_start_time
|
144
|
-
.get()
|
145
|
-
.map(|instant| Local::now() - instant)
|
157
|
+
pub fn get_response_time(&self) -> Duration {
|
158
|
+
self.inner.start_instant.elapsed()
|
146
159
|
}
|
147
160
|
|
148
161
|
pub fn set_response_format(&self, format: ResponseFormat) {
|
@@ -152,6 +165,10 @@ impl HttpRequestContext {
|
|
152
165
|
pub fn response_format(&self) -> &ResponseFormat {
|
153
166
|
self.inner.response_format.get().unwrap()
|
154
167
|
}
|
168
|
+
|
169
|
+
pub fn supported_encoding_set(&self) -> Option<&Vec<HeaderValue>> {
|
170
|
+
self.inner.supported_encoding_set.get()
|
171
|
+
}
|
155
172
|
}
|
156
173
|
|
157
174
|
const SERVER_TOKEN_VERSION: HeaderValue =
|
@@ -170,12 +187,7 @@ impl Service<Request<Incoming>> for ItsiHttpService {
|
|
170
187
|
let accept: ResponseFormat = req.accept().into();
|
171
188
|
let accept_clone = accept.clone();
|
172
189
|
let is_single_mode = self.server_params.workers == 1;
|
173
|
-
|
174
|
-
.headers()
|
175
|
-
.get_all(ACCEPT_ENCODING)
|
176
|
-
.into_iter()
|
177
|
-
.cloned()
|
178
|
-
.collect::<Vec<_>>();
|
190
|
+
|
179
191
|
let request_timeout = self.server_params.request_timeout;
|
180
192
|
let is_ruby_request = Arc::new(AtomicBool::new(false));
|
181
193
|
let irr_clone = is_ruby_request.clone();
|
@@ -187,7 +199,6 @@ impl Service<Request<Incoming>> for ItsiHttpService {
|
|
187
199
|
self_clone,
|
188
200
|
matching_pattern,
|
189
201
|
accept_clone.clone(),
|
190
|
-
supported_encoding_set,
|
191
202
|
irr_clone,
|
192
203
|
);
|
193
204
|
let mut depth = 0;
|
@@ -229,8 +240,8 @@ impl Service<Request<Incoming>> for ItsiHttpService {
|
|
229
240
|
Ok(resp)
|
230
241
|
};
|
231
242
|
|
232
|
-
|
233
|
-
|
243
|
+
if let Some(timeout_duration) = request_timeout {
|
244
|
+
Box::pin(async move {
|
234
245
|
match timeout(timeout_duration, service_future).await {
|
235
246
|
Ok(result) => result,
|
236
247
|
Err(_) => {
|
@@ -249,9 +260,9 @@ impl Service<Request<Incoming>> for ItsiHttpService {
|
|
249
260
|
Ok(TIMEOUT_RESPONSE.to_http_response(accept).await)
|
250
261
|
}
|
251
262
|
}
|
252
|
-
}
|
253
|
-
|
254
|
-
|
255
|
-
}
|
263
|
+
})
|
264
|
+
} else {
|
265
|
+
Box::pin(service_future)
|
266
|
+
}
|
256
267
|
}
|
257
268
|
}
|
@@ -51,6 +51,10 @@ pub static ROOT_STATIC_FILE_SERVER: LazyLock<StaticFileServer> = LazyLock::new(|
|
|
51
51
|
not_found_behavior: NotFoundBehavior::Error(ErrorResponse::not_found()),
|
52
52
|
serve_hidden_files: false,
|
53
53
|
allowed_extensions: vec!["html".to_string(), "css".to_string(), "js".to_string()],
|
54
|
+
miss_cache: Cache::builder()
|
55
|
+
.max_capacity(1000)
|
56
|
+
.time_to_live(Duration::from_secs(1))
|
57
|
+
.build(),
|
54
58
|
})
|
55
59
|
.unwrap()
|
56
60
|
});
|
@@ -85,6 +89,7 @@ pub struct StaticFileServerConfig {
|
|
85
89
|
pub headers: Option<HashMap<String, String>>,
|
86
90
|
pub serve_hidden_files: bool,
|
87
91
|
pub allowed_extensions: Vec<String>,
|
92
|
+
pub miss_cache: Cache<String, NotFoundBehavior>,
|
88
93
|
}
|
89
94
|
|
90
95
|
#[derive(Debug, Clone)]
|
@@ -389,6 +394,29 @@ impl StaticFileServer {
|
|
389
394
|
abs_path: &str,
|
390
395
|
accept: ResponseFormat,
|
391
396
|
) -> std::result::Result<ResolvedAsset, NotFoundBehavior> {
|
397
|
+
let ext_opt = Path::new(key)
|
398
|
+
.extension()
|
399
|
+
.and_then(|e| e.to_str())
|
400
|
+
.map(|s| s.to_lowercase());
|
401
|
+
|
402
|
+
// If the allowed list is non-empty, enforce membership
|
403
|
+
if !self.allowed_extensions.is_empty() {
|
404
|
+
match ext_opt {
|
405
|
+
Some(ref ext)
|
406
|
+
if self
|
407
|
+
.allowed_extensions
|
408
|
+
.iter()
|
409
|
+
.any(|ae| ae.eq_ignore_ascii_case(ext)) => {}
|
410
|
+
None if self.config.try_html_extension => {}
|
411
|
+
_ => {
|
412
|
+
return Err(self.config.not_found_behavior.clone());
|
413
|
+
}
|
414
|
+
}
|
415
|
+
}
|
416
|
+
|
417
|
+
if let Some(cached_nf) = self.miss_cache.get(key) {
|
418
|
+
return Err(cached_nf.clone());
|
419
|
+
}
|
392
420
|
// First check if we have a cached mapping for this key
|
393
421
|
if let Some(path) = self.key_to_path.lock().await.get(key) {
|
394
422
|
// Check if the cached entry is still valid
|
@@ -449,7 +477,6 @@ impl StaticFileServer {
|
|
449
477
|
|
450
478
|
let mut full_path = self.config.root_dir.clone();
|
451
479
|
full_path.push(normalized_path);
|
452
|
-
debug!("Resolving path {:?}", full_path);
|
453
480
|
// Check if path exists and is a file
|
454
481
|
match tokio::fs::metadata(&full_path).await {
|
455
482
|
Ok(metadata) => {
|
@@ -561,7 +588,6 @@ impl StaticFileServer {
|
|
561
588
|
}
|
562
589
|
Err(_) => {
|
563
590
|
// Path doesn't exist, try with .html extension if configured
|
564
|
-
debug!("Path doesn't exist");
|
565
591
|
if self.config.try_html_extension {
|
566
592
|
let mut html_path = full_path.clone();
|
567
593
|
html_path.set_extension("html");
|
@@ -592,7 +618,9 @@ impl StaticFileServer {
|
|
592
618
|
}
|
593
619
|
|
594
620
|
// If we get here, we couldn't resolve the key to a file
|
595
|
-
|
621
|
+
let nf = self.config.not_found_behavior.clone();
|
622
|
+
self.miss_cache.insert(key.to_string(), nf.clone());
|
623
|
+
Err(nf)
|
596
624
|
}
|
597
625
|
|
598
626
|
async fn stream_file_range(
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: itsi-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
@@ -188,8 +188,8 @@ licenses:
|
|
188
188
|
- MIT
|
189
189
|
metadata:
|
190
190
|
homepage_uri: https://itsi.fyi
|
191
|
-
source_code_uri: https://github.com/wouterken/itsi
|
192
|
-
changelog_uri: https://github.com/wouterken/itsi/
|
191
|
+
source_code_uri: https://github.com/wouterken/itsi
|
192
|
+
changelog_uri: https://github.com/wouterken/itsi/blob/main/CHANGELOG.md
|
193
193
|
rdoc_options: []
|
194
194
|
require_paths:
|
195
195
|
- lib
|