itsi-server 0.2.14 → 0.2.15
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 +100 -151
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_server/Cargo.lock +1 -1
- data/ext/itsi_server/Cargo.toml +3 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +8 -6
- data/ext/itsi_server/src/services/mime_types.rs +2891 -1413
- data/ext/itsi_server/src/services/static_file_server.rs +140 -108
- data/ext/itsi_tracing/Cargo.toml +1 -1
- data/lib/itsi/server/version.rb +1 -1
- metadata +18 -18
@@ -14,12 +14,13 @@ use http::{
|
|
14
14
|
header::{
|
15
15
|
self, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE, ETAG, LAST_MODIFIED,
|
16
16
|
},
|
17
|
-
HeaderValue, Response, StatusCode,
|
17
|
+
HeaderName, HeaderValue, Response, StatusCode,
|
18
18
|
};
|
19
19
|
use http_body_util::{combinators::BoxBody, Full};
|
20
20
|
use itsi_error::Result;
|
21
|
-
use
|
21
|
+
use parking_lot::{Mutex, RwLock};
|
22
22
|
use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC};
|
23
|
+
use quick_cache::sync::Cache;
|
23
24
|
use serde::Deserialize;
|
24
25
|
use serde_json::json;
|
25
26
|
use sha2::{Digest, Sha256};
|
@@ -34,7 +35,6 @@ use std::{
|
|
34
35
|
sync::{Arc, LazyLock},
|
35
36
|
time::{Duration, Instant, SystemTime},
|
36
37
|
};
|
37
|
-
use tokio::sync::Mutex;
|
38
38
|
use tokio::{fs::File, io::AsyncReadExt};
|
39
39
|
|
40
40
|
use super::mime_types::get_mime_type;
|
@@ -51,10 +51,7 @@ 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::
|
55
|
-
.max_capacity(1000)
|
56
|
-
.time_to_live(Duration::from_secs(1))
|
57
|
-
.build(),
|
54
|
+
miss_cache: Arc::new(Cache::new(1000)),
|
58
55
|
})
|
59
56
|
.unwrap()
|
60
57
|
});
|
@@ -89,14 +86,14 @@ pub struct StaticFileServerConfig {
|
|
89
86
|
pub headers: Option<HashMap<String, String>>,
|
90
87
|
pub serve_hidden_files: bool,
|
91
88
|
pub allowed_extensions: Vec<String>,
|
92
|
-
pub miss_cache: Cache<String, NotFoundBehavior
|
89
|
+
pub miss_cache: Arc<Cache<String, NotFoundBehavior>>,
|
93
90
|
}
|
94
91
|
|
95
92
|
#[derive(Debug, Clone)]
|
96
93
|
pub struct StaticFileServer {
|
97
94
|
config: Arc<StaticFileServerConfig>,
|
98
95
|
key_to_path: Arc<Mutex<HashMap<String, PathBuf>>>,
|
99
|
-
cache: Cache<PathBuf, CacheEntry
|
96
|
+
cache: Arc<Cache<PathBuf, Arc<CacheEntry>>>,
|
100
97
|
}
|
101
98
|
|
102
99
|
impl Deref for StaticFileServer {
|
@@ -110,36 +107,50 @@ impl Deref for StaticFileServer {
|
|
110
107
|
#[derive(Clone, Debug)]
|
111
108
|
struct CacheEntry {
|
112
109
|
content: Arc<Bytes>,
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
etag: String,
|
110
|
+
br: Option<Arc<Bytes>>,
|
111
|
+
gz: Option<Arc<Bytes>>,
|
112
|
+
zstd: Option<Arc<Bytes>>,
|
113
|
+
deflate: Option<Arc<Bytes>>,
|
118
114
|
last_modified: SystemTime,
|
119
|
-
|
115
|
+
headers_ct: HeaderValue,
|
116
|
+
headers_etag: HeaderValue,
|
117
|
+
headers_cl: HeaderValue,
|
118
|
+
last_modified_http_date: HeaderValue,
|
119
|
+
last_checked: Arc<RwLock<Instant>>,
|
120
120
|
}
|
121
121
|
|
122
|
+
static HEADER_VALUE_ZSTD: HeaderValue = HeaderValue::from_static("zstd");
|
123
|
+
static HEADER_VALUE_GZIP: HeaderValue = HeaderValue::from_static("gzip");
|
124
|
+
static HEADER_VALUE_BR: HeaderValue = HeaderValue::from_static("br");
|
125
|
+
static HEADER_VALUE_DEFLATE: HeaderValue = HeaderValue::from_static("deflate");
|
126
|
+
|
122
127
|
impl CacheEntry {
|
123
128
|
pub fn suggest_content_for(
|
124
129
|
&self,
|
125
130
|
supported_encodings: &[HeaderValue],
|
126
|
-
) -> (Arc<Bytes>, Option
|
131
|
+
) -> (Arc<Bytes>, Option<HeaderValue>) {
|
127
132
|
for encoding_header in supported_encodings {
|
128
133
|
if let Ok(header_value) = encoding_header.to_str() {
|
129
134
|
for header_value in header_value.split(",").map(|hv| hv.trim()) {
|
130
135
|
for algo in header_value.split(";").map(|hv| hv.trim()) {
|
131
136
|
match algo {
|
132
|
-
"zstd" if self.
|
133
|
-
return (
|
137
|
+
"zstd" if self.zstd.is_some() => {
|
138
|
+
return (
|
139
|
+
self.zstd.clone().unwrap(),
|
140
|
+
Some(HEADER_VALUE_ZSTD.clone()),
|
141
|
+
)
|
134
142
|
}
|
135
|
-
"gzip" if self.
|
136
|
-
return (self.
|
143
|
+
"gzip" if self.gz.is_some() => {
|
144
|
+
return (self.gz.clone().unwrap(), Some(HEADER_VALUE_GZIP.clone()))
|
137
145
|
}
|
138
|
-
"br" if self.
|
139
|
-
return (self.
|
146
|
+
"br" if self.br.is_some() => {
|
147
|
+
return (self.br.clone().unwrap(), Some(HEADER_VALUE_BR.clone()))
|
140
148
|
}
|
141
|
-
"deflate" if self.
|
142
|
-
return (
|
149
|
+
"deflate" if self.deflate.is_some() => {
|
150
|
+
return (
|
151
|
+
self.deflate.clone().unwrap(),
|
152
|
+
Some(HEADER_VALUE_DEFLATE.clone()),
|
153
|
+
)
|
143
154
|
}
|
144
155
|
_ => {}
|
145
156
|
}
|
@@ -158,7 +169,7 @@ pub enum ServeRange {
|
|
158
169
|
}
|
159
170
|
|
160
171
|
impl CacheEntry {
|
161
|
-
async fn new(path: PathBuf) -> Result<Self
|
172
|
+
async fn new(path: PathBuf) -> Result<Arc<Self>> {
|
162
173
|
let (bytes, last_modified) = read_entire_file(&path).await?;
|
163
174
|
let etag = {
|
164
175
|
let mut hasher = Sha256::new();
|
@@ -166,23 +177,29 @@ impl CacheEntry {
|
|
166
177
|
let result = hasher.finalize();
|
167
178
|
general_purpose::STANDARD.encode(result)
|
168
179
|
};
|
169
|
-
|
180
|
+
let headers_ct = get_mime_type(&path);
|
181
|
+
let headers_etag = format!(r#"W/"{etag}""#).parse().unwrap();
|
182
|
+
let headers_cl = ((bytes.len() as u64).to_string()).parse().unwrap();
|
183
|
+
Ok(Arc::new(CacheEntry {
|
170
184
|
content: Arc::new(bytes),
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
185
|
+
gz: read_variant(&path, "gz").await.map(Arc::new),
|
186
|
+
br: read_variant(&path, "br").await.map(Arc::new),
|
187
|
+
zstd: read_variant(&path, "zstd").await.map(Arc::new),
|
188
|
+
deflate: read_variant(&path, "deflate").await.map(Arc::new),
|
189
|
+
headers_ct,
|
190
|
+
headers_etag,
|
191
|
+
headers_cl,
|
175
192
|
last_modified,
|
176
|
-
|
177
|
-
last_checked: Instant::now(),
|
178
|
-
})
|
193
|
+
last_modified_http_date: format_http_date_header(last_modified),
|
194
|
+
last_checked: Arc::new(RwLock::new(Instant::now())),
|
195
|
+
}))
|
179
196
|
}
|
180
197
|
|
181
198
|
async fn new_virtual_listing(
|
182
199
|
path: PathBuf,
|
183
200
|
config: &StaticFileServerConfig,
|
184
201
|
accept: ResponseFormat,
|
185
|
-
) -> Self {
|
202
|
+
) -> Arc<Self> {
|
186
203
|
let directory_listing: Bytes =
|
187
204
|
generate_directory_listing(path.parent().unwrap(), config, accept)
|
188
205
|
.await
|
@@ -194,16 +211,23 @@ impl CacheEntry {
|
|
194
211
|
let result = hasher.finalize();
|
195
212
|
general_purpose::STANDARD.encode(result)
|
196
213
|
};
|
197
|
-
|
214
|
+
let headers_ct = get_mime_type(&path);
|
215
|
+
let headers_etag = format!(r#"W/"{etag}""#).parse().unwrap();
|
216
|
+
let headers_cl = directory_listing.len().to_string().parse().unwrap();
|
217
|
+
let last_modified = SystemTime::now();
|
218
|
+
Arc::new(CacheEntry {
|
198
219
|
content: Arc::new(directory_listing),
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
220
|
+
gz: None,
|
221
|
+
br: None,
|
222
|
+
zstd: None,
|
223
|
+
deflate: None,
|
224
|
+
headers_ct,
|
225
|
+
headers_etag,
|
226
|
+
headers_cl,
|
227
|
+
last_modified,
|
228
|
+
last_modified_http_date: format_http_date_header(last_modified),
|
229
|
+
last_checked: Arc::new(RwLock::new(Instant::now())),
|
230
|
+
})
|
207
231
|
}
|
208
232
|
}
|
209
233
|
|
@@ -221,7 +245,7 @@ struct ServeCacheArgs<'a>(
|
|
221
245
|
|
222
246
|
impl StaticFileServer {
|
223
247
|
pub fn new(config: StaticFileServerConfig) -> Result<Self> {
|
224
|
-
let cache = Cache::
|
248
|
+
let cache = Arc::new(Cache::new(config.max_entries as usize));
|
225
249
|
if !config.root_dir.exists() {
|
226
250
|
return Err(ItsiError::InternalError(format!(
|
227
251
|
"Root directory {} for static file server doesn't exist",
|
@@ -417,11 +441,16 @@ impl StaticFileServer {
|
|
417
441
|
if let Some(cached_nf) = self.miss_cache.get(key) {
|
418
442
|
return Err(cached_nf.clone());
|
419
443
|
}
|
420
|
-
|
421
|
-
|
444
|
+
|
445
|
+
let path = {
|
446
|
+
let guard = self.key_to_path.lock();
|
447
|
+
guard.get(key).cloned()
|
448
|
+
};
|
449
|
+
|
450
|
+
if let Some(path) = path {
|
422
451
|
// Check if the cached entry is still valid
|
423
|
-
if let Some(entry) = self.cache.get(path) {
|
424
|
-
let last_check_elapsed = entry.last_checked.elapsed();
|
452
|
+
if let Some(entry) = self.cache.get(&path) {
|
453
|
+
let last_check_elapsed = entry.last_checked.read().elapsed();
|
425
454
|
if last_check_elapsed < self.config.recheck_interval {
|
426
455
|
// Entry is still fresh, use it
|
427
456
|
return Ok(ResolvedAsset {
|
@@ -433,15 +462,13 @@ impl StaticFileServer {
|
|
433
462
|
}
|
434
463
|
|
435
464
|
// Entry is stale, check if file has changed
|
436
|
-
if let Ok(metadata) = tokio::fs::metadata(path).await {
|
465
|
+
if let Ok(metadata) = tokio::fs::metadata(&path).await {
|
437
466
|
if metadata
|
438
467
|
.modified()
|
439
468
|
.is_ok_and(|modified| modified == entry.last_modified)
|
440
469
|
{
|
441
470
|
// File hasn't changed, just update last_checked
|
442
|
-
|
443
|
-
entry.last_checked = Instant::now();
|
444
|
-
self.cache.insert(path.clone(), entry.clone());
|
471
|
+
*entry.last_checked.write() = Instant::now();
|
445
472
|
return Ok(ResolvedAsset {
|
446
473
|
path: path.clone(),
|
447
474
|
cache_entry: Some(entry.clone()),
|
@@ -453,17 +480,19 @@ impl StaticFileServer {
|
|
453
480
|
// File has changed, check if it's still cacheable
|
454
481
|
if metadata.len() > self.config.max_file_size {
|
455
482
|
// File is now too large, remove from cache
|
456
|
-
self.cache.
|
457
|
-
self.key_to_path.lock().
|
483
|
+
self.cache.remove(&path);
|
484
|
+
self.key_to_path.lock().remove(key);
|
458
485
|
}
|
459
486
|
}
|
460
487
|
}
|
461
488
|
}
|
462
489
|
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
490
|
+
let normalized_path = normalize_path(if key.contains('%') {
|
491
|
+
percent_decode_str(key).decode_utf8_lossy()
|
492
|
+
} else {
|
493
|
+
Cow::Borrowed(key)
|
494
|
+
})
|
495
|
+
.ok_or(NotFoundBehavior::Error(NOT_FOUND_RESPONSE.clone()))?;
|
467
496
|
|
468
497
|
if !self.config.serve_hidden_files
|
469
498
|
&& normalized_path
|
@@ -484,7 +513,6 @@ impl StaticFileServer {
|
|
484
513
|
let cache_entry = if metadata.len() <= self.config.max_file_size {
|
485
514
|
self.key_to_path
|
486
515
|
.lock()
|
487
|
-
.await
|
488
516
|
.insert(key.to_string(), full_path.clone());
|
489
517
|
let cache_entry = CacheEntry::new(full_path.clone()).await.unwrap();
|
490
518
|
self.cache.insert(full_path.clone(), cache_entry.clone());
|
@@ -547,7 +575,6 @@ impl StaticFileServer {
|
|
547
575
|
let index_path = index_file.unwrap();
|
548
576
|
self.key_to_path
|
549
577
|
.lock()
|
550
|
-
.await
|
551
578
|
.insert(key.to_string(), index_path.clone());
|
552
579
|
let cache_entry = CacheEntry::new(index_path.clone()).await.unwrap();
|
553
580
|
self.cache.insert(index_path.clone(), cache_entry.clone());
|
@@ -574,7 +601,6 @@ impl StaticFileServer {
|
|
574
601
|
.await;
|
575
602
|
self.key_to_path
|
576
603
|
.lock()
|
577
|
-
.await
|
578
604
|
.insert(key.to_string(), virtual_path.clone());
|
579
605
|
self.cache.insert(virtual_path.clone(), cache_entry.clone());
|
580
606
|
return Ok(ResolvedAsset {
|
@@ -596,7 +622,6 @@ impl StaticFileServer {
|
|
596
622
|
if html_meta.is_file() {
|
597
623
|
self.key_to_path
|
598
624
|
.lock()
|
599
|
-
.await
|
600
625
|
.insert(key.to_string(), html_path.clone());
|
601
626
|
let cache_entry = if html_meta.len() <= self.config.max_file_size {
|
602
627
|
let cache_entry = CacheEntry::new(html_path.clone()).await.unwrap();
|
@@ -764,7 +789,7 @@ impl StaticFileServer {
|
|
764
789
|
content_length.to_string()
|
765
790
|
},
|
766
791
|
)
|
767
|
-
.header("Last-Modified",
|
792
|
+
.header("Last-Modified", format_http_date_header(last_modified));
|
768
793
|
|
769
794
|
if let Some(range) = content_range {
|
770
795
|
builder = builder.header("Content-Range", range);
|
@@ -783,8 +808,8 @@ impl StaticFileServer {
|
|
783
808
|
None,
|
784
809
|
None,
|
785
810
|
get_mime_type(&file),
|
786
|
-
(end_idx - start) as usize,
|
787
|
-
last_modified,
|
811
|
+
((end_idx - start) as usize).to_string().parse().unwrap(),
|
812
|
+
format_http_date_header(last_modified),
|
788
813
|
content_range,
|
789
814
|
&self.headers,
|
790
815
|
self.stream_file_range(file, start, end_idx).await.unwrap(),
|
@@ -795,8 +820,8 @@ impl StaticFileServer {
|
|
795
820
|
None,
|
796
821
|
None,
|
797
822
|
get_mime_type(&file),
|
798
|
-
content_length as usize,
|
799
|
-
last_modified,
|
823
|
+
(content_length as usize).to_string().parse().unwrap(),
|
824
|
+
format_http_date_header(last_modified),
|
800
825
|
content_range,
|
801
826
|
&self.headers,
|
802
827
|
self.stream_file(file).await.unwrap(),
|
@@ -870,7 +895,10 @@ impl StaticFileServer {
|
|
870
895
|
content_length.to_string()
|
871
896
|
},
|
872
897
|
)
|
873
|
-
.header(
|
898
|
+
.header(
|
899
|
+
"Last-Modified",
|
900
|
+
format_http_date_header(cache_entry.last_modified),
|
901
|
+
);
|
874
902
|
|
875
903
|
if let Some(range) = content_range {
|
876
904
|
builder = builder.header("Content-Range", range);
|
@@ -883,19 +911,13 @@ impl StaticFileServer {
|
|
883
911
|
let start_idx = start as usize;
|
884
912
|
let end_idx = std::cmp::min((adjusted_end + 1) as usize, cache_entry.content.len());
|
885
913
|
let range_bytes = cache_entry.content.slice(start_idx..end_idx);
|
886
|
-
let etag = {
|
887
|
-
let mut hasher = Sha256::new();
|
888
|
-
hasher.update(&range_bytes);
|
889
|
-
let result = hasher.finalize();
|
890
|
-
general_purpose::STANDARD.encode(result)
|
891
|
-
};
|
892
914
|
build_file_response(
|
893
915
|
status,
|
894
916
|
None,
|
895
|
-
Some(
|
896
|
-
|
897
|
-
range_bytes.len(),
|
898
|
-
cache_entry.
|
917
|
+
Some(cache_entry.headers_etag.clone()),
|
918
|
+
cache_entry.headers_ct.clone(),
|
919
|
+
range_bytes.len().to_string().parse().unwrap(),
|
920
|
+
cache_entry.last_modified_http_date.clone(),
|
899
921
|
content_range,
|
900
922
|
&self.headers,
|
901
923
|
BoxBody::new(Full::new(range_bytes)),
|
@@ -907,10 +929,10 @@ impl StaticFileServer {
|
|
907
929
|
build_file_response(
|
908
930
|
status,
|
909
931
|
encoding,
|
910
|
-
Some(
|
911
|
-
|
912
|
-
|
913
|
-
cache_entry.
|
932
|
+
Some(cache_entry.headers_etag.clone()),
|
933
|
+
cache_entry.headers_ct.clone(),
|
934
|
+
cache_entry.headers_cl.clone(),
|
935
|
+
cache_entry.last_modified_http_date.clone(),
|
914
936
|
content_range,
|
915
937
|
&self.headers,
|
916
938
|
body,
|
@@ -920,16 +942,11 @@ impl StaticFileServer {
|
|
920
942
|
|
921
943
|
pub async fn invalidate_cache(&self, path: &Path) {
|
922
944
|
if let Ok(path_buf) = path.to_path_buf().canonicalize() {
|
923
|
-
self.cache.
|
945
|
+
self.cache.remove(&path_buf);
|
924
946
|
}
|
925
947
|
}
|
926
948
|
}
|
927
949
|
|
928
|
-
fn format_http_date(last_modified: SystemTime) -> String {
|
929
|
-
let datetime = DateTime::<Utc>::from(last_modified);
|
930
|
-
datetime.format("%a, %d %b %Y %H:%M:%S GMT").to_string()
|
931
|
-
}
|
932
|
-
|
933
950
|
async fn read_entire_file(path: &Path) -> std::io::Result<(Bytes, SystemTime)> {
|
934
951
|
let metadata = tokio::fs::metadata(path).await?;
|
935
952
|
let last_modified = metadata.modified()?;
|
@@ -962,6 +979,14 @@ async fn read_variant(path: &Path, ext: &str) -> Option<Bytes> {
|
|
962
979
|
None
|
963
980
|
}
|
964
981
|
|
982
|
+
fn format_http_date_header(time: SystemTime) -> HeaderValue {
|
983
|
+
DateTime::<Utc>::from(time)
|
984
|
+
.format("%a, %d %b %Y %H:%M:%S GMT")
|
985
|
+
.to_string()
|
986
|
+
.parse()
|
987
|
+
.unwrap()
|
988
|
+
}
|
989
|
+
|
965
990
|
fn build_ok_body(bytes: Arc<Bytes>) -> BoxBody<Bytes, Infallible> {
|
966
991
|
BoxBody::new(Full::new(bytes.as_ref().clone()))
|
967
992
|
}
|
@@ -977,39 +1002,46 @@ fn build_not_modified_response() -> http::Response<BoxBody<Bytes, Infallible>> {
|
|
977
1002
|
#[allow(clippy::too_many_arguments)]
|
978
1003
|
fn build_file_response(
|
979
1004
|
status: StatusCode,
|
980
|
-
content_encoding: Option
|
981
|
-
etag: Option
|
982
|
-
content_type:
|
983
|
-
content_length:
|
984
|
-
|
1005
|
+
content_encoding: Option<HeaderValue>,
|
1006
|
+
etag: Option<HeaderValue>,
|
1007
|
+
content_type: HeaderValue,
|
1008
|
+
content_length: HeaderValue,
|
1009
|
+
last_modified_http_date: HeaderValue,
|
985
1010
|
range_header: Option<String>,
|
986
1011
|
headers: &Option<HashMap<String, String>>,
|
987
1012
|
body: BoxBody<Bytes, Infallible>,
|
988
1013
|
) -> http::Response<BoxBody<Bytes, Infallible>> {
|
989
|
-
let mut
|
990
|
-
.status(status)
|
991
|
-
.header(CONTENT_TYPE, content_type)
|
992
|
-
.header(CONTENT_LENGTH, content_length)
|
993
|
-
.header(LAST_MODIFIED, format_http_date(last_modified));
|
1014
|
+
let mut response = Response::new(body);
|
994
1015
|
|
995
|
-
|
996
|
-
|
997
|
-
|
1016
|
+
*response.status_mut() = status;
|
1017
|
+
let headers_mut = response.headers_mut();
|
1018
|
+
|
1019
|
+
headers_mut.insert(CONTENT_TYPE, content_type);
|
1020
|
+
headers_mut.insert(CONTENT_LENGTH, content_length);
|
1021
|
+
headers_mut.insert(LAST_MODIFIED, last_modified_http_date);
|
998
1022
|
|
999
1023
|
if let Some(content_encoding) = content_encoding {
|
1000
|
-
|
1024
|
+
headers_mut.insert(CONTENT_ENCODING, content_encoding);
|
1025
|
+
}
|
1026
|
+
|
1027
|
+
if let Some(etag) = etag {
|
1028
|
+
headers_mut.insert(ETAG, etag);
|
1001
1029
|
}
|
1002
1030
|
|
1003
|
-
if let Some(range) = range_header {
|
1004
|
-
|
1031
|
+
if let Some(range) = range_header.and_then(|r| r.parse().ok()) {
|
1032
|
+
headers_mut.insert(CONTENT_RANGE, range);
|
1005
1033
|
}
|
1034
|
+
|
1006
1035
|
if let Some(headers) = headers {
|
1007
1036
|
for (key, value) in headers {
|
1008
|
-
|
1037
|
+
if let (Ok(parsed_key), Ok(parsed_value)) =
|
1038
|
+
(key.parse::<HeaderName>(), value.parse::<HeaderValue>())
|
1039
|
+
{
|
1040
|
+
headers_mut.insert(parsed_key, parsed_value);
|
1041
|
+
}
|
1009
1042
|
}
|
1010
1043
|
}
|
1011
|
-
|
1012
|
-
builder.body(body).unwrap()
|
1044
|
+
response
|
1013
1045
|
}
|
1014
1046
|
|
1015
1047
|
// Helper function to check if a file is too old based on If-Modified-Since
|
@@ -1049,7 +1081,7 @@ fn normalize_path(path: Cow<'_, str>) -> Option<PathBuf> {
|
|
1049
1081
|
#[derive(Debug)]
|
1050
1082
|
struct ResolvedAsset {
|
1051
1083
|
path: PathBuf,
|
1052
|
-
cache_entry: Option<CacheEntry
|
1084
|
+
cache_entry: Option<Arc<CacheEntry>>,
|
1053
1085
|
metadata: Option<Metadata>,
|
1054
1086
|
redirect_to: Option<String>,
|
1055
1087
|
}
|
data/ext/itsi_tracing/Cargo.toml
CHANGED
data/lib/itsi/server/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: itsi-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
@@ -10,61 +10,61 @@ cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
|
-
name:
|
13
|
+
name: json
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
15
15
|
requirements:
|
16
|
-
- - "
|
16
|
+
- - "~>"
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: '
|
18
|
+
version: '2'
|
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: '
|
25
|
+
version: '2'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
27
|
+
name: prism
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
29
29
|
requirements:
|
30
30
|
- - "~>"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '1.4'
|
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: '
|
39
|
+
version: '1.4'
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
|
-
name:
|
41
|
+
name: rack
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
|
-
- - "
|
44
|
+
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
46
|
+
version: '1.6'
|
47
47
|
type: :runtime
|
48
48
|
prerelease: false
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
|
-
- - "
|
51
|
+
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
53
|
+
version: '1.6'
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
|
-
name:
|
55
|
+
name: rb_sys
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
60
|
+
version: 0.9.91
|
61
61
|
type: :runtime
|
62
62
|
prerelease: false
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
67
|
+
version: 0.9.91
|
68
68
|
- !ruby/object:Gem::Dependency
|
69
69
|
name: ruby-lsp
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -536,7 +536,7 @@ files:
|
|
536
536
|
- lib/shell_completions/completions.rb
|
537
537
|
homepage: https://itsi.fyi
|
538
538
|
licenses:
|
539
|
-
-
|
539
|
+
- LGPL-3.0
|
540
540
|
metadata:
|
541
541
|
homepage_uri: https://itsi.fyi
|
542
542
|
source_code_uri: https://github.com/wouterken/itsi
|