itsi 0.2.15 → 0.2.16

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Cargo.lock +74 -74
  4. data/crates/itsi_scheduler/Cargo.toml +1 -1
  5. data/crates/itsi_scheduler/extconf.rb +3 -1
  6. data/crates/itsi_server/Cargo.lock +1 -1
  7. data/crates/itsi_server/Cargo.toml +1 -1
  8. data/crates/itsi_server/extconf.rb +3 -1
  9. data/crates/itsi_server/src/lib.rs +1 -0
  10. data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +2 -2
  11. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +9 -11
  12. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +6 -1
  13. data/crates/itsi_server/src/server/binds/listener.rs +4 -1
  14. data/crates/itsi_server/src/server/http_message_types.rs +1 -1
  15. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +32 -34
  16. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +3 -4
  17. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +23 -38
  18. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +65 -14
  19. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +1 -1
  20. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +1 -1
  21. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +21 -8
  22. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +1 -5
  23. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +12 -3
  24. data/crates/itsi_server/src/server/process_worker.rs +2 -1
  25. data/crates/itsi_server/src/server/serve_strategy/acceptor.rs +96 -0
  26. data/crates/itsi_server/src/server/serve_strategy/mod.rs +1 -0
  27. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +80 -136
  28. data/crates/itsi_server/src/server/thread_worker.rs +10 -3
  29. data/crates/itsi_server/src/services/itsi_http_service.rs +26 -21
  30. data/crates/itsi_server/src/services/mime_types.rs +185 -183
  31. data/crates/itsi_server/src/services/rate_limiter.rs +16 -34
  32. data/crates/itsi_server/src/services/static_file_server.rs +7 -13
  33. data/docs/content/features/_index.md +1 -1
  34. data/examples/rails_with_static_assets/Gemfile.lock +1 -1
  35. data/examples/rails_with_static_assets/Itsi.rb +4 -1
  36. data/gems/scheduler/Cargo.lock +15 -15
  37. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  38. data/gems/server/Cargo.lock +73 -73
  39. data/gems/server/lib/itsi/server/config/config_helpers.rb +1 -2
  40. data/gems/server/lib/itsi/server/config/middleware/etag.md +3 -7
  41. data/gems/server/lib/itsi/server/config/middleware/etag.rb +2 -4
  42. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +1 -1
  43. data/gems/server/lib/itsi/server/config/options/send_buffer_size.md +15 -0
  44. data/gems/server/lib/itsi/server/config/options/send_buffer_size.rb +19 -0
  45. data/gems/server/lib/itsi/server/config.rb +24 -25
  46. data/gems/server/lib/itsi/server/route_tester.rb +1 -1
  47. data/gems/server/lib/itsi/server/version.rb +1 -1
  48. data/gems/server/test/middleware/etag.rb +3 -3
  49. data/gems/server/test/options/ruby_thread_request_backlog_size.rb +2 -3
  50. data/lib/itsi/version.rb +1 -1
  51. data/tasks.txt +8 -7
  52. metadata +8 -5
@@ -1,4 +1,5 @@
1
1
  use async_trait::async_trait;
2
+ use parking_lot::{Mutex, RwLock};
2
3
  use rand::Rng;
3
4
  use redis::aio::ConnectionManager;
4
5
  use redis::{Client, RedisError, Script};
@@ -6,9 +7,9 @@ use serde::Deserialize;
6
7
  use std::any::Any;
7
8
  use std::collections::{HashMap, HashSet};
8
9
  use std::result::Result;
9
- use std::sync::{Arc, LazyLock, Mutex};
10
+ use std::sync::{Arc, LazyLock};
10
11
  use std::time::{Duration, Instant};
11
- use tokio::sync::{Mutex as AsyncMutex, RwLock};
12
+ use tokio::sync::Mutex as AsyncMutex;
12
13
  use tokio::time::timeout;
13
14
  use tracing::warn;
14
15
  use url::Url;
@@ -242,10 +243,10 @@ impl InMemoryRateLimiter {
242
243
  /// Cleans up expired entries
243
244
  async fn cleanup(&self) {
244
245
  // Try to get the write lock, but fail open if we can't
245
- if let Ok(mut entries) = self.entries.try_write() {
246
- let now = Instant::now();
247
- entries.retain(|_, entry| entry.expires_at > now);
248
- }
246
+ let now = Instant::now();
247
+ self.entries
248
+ .write()
249
+ .retain(|_, entry| entry.expires_at > now);
249
250
  }
250
251
 
251
252
  /// Bans an IP address for the specified duration
@@ -258,12 +259,7 @@ impl InMemoryRateLimiter {
258
259
  let now = Instant::now();
259
260
  let ban_key = format!("ban:ip:{}", ip);
260
261
 
261
- let mut entries = self.entries.try_write().map_err(|e| {
262
- tracing::error!("Failed to acquire write lock: {}", e);
263
- RateLimitError::LockError
264
- })?;
265
-
266
- entries.insert(
262
+ self.entries.write().insert(
267
263
  ban_key,
268
264
  RateLimitEntry {
269
265
  count: 1, // Use count=1 to indicate banned
@@ -279,12 +275,7 @@ impl InMemoryRateLimiter {
279
275
  let now = Instant::now();
280
276
  let ban_key = format!("ban:ip:{}", ip);
281
277
 
282
- let entries = self.entries.try_read().map_err(|e| {
283
- tracing::error!("Failed to acquire read lock: {}", e);
284
- RateLimitError::LockError
285
- })?;
286
-
287
- if let Some(entry) = entries.get(&ban_key) {
278
+ if let Some(entry) = self.entries.read().get(&ban_key) {
288
279
  if entry.expires_at > now {
289
280
  // IP is banned, return a generic reason since we don't store reasons
290
281
  return Ok(Some("IP address banned".to_string()));
@@ -310,7 +301,7 @@ impl RateLimiter for InMemoryRateLimiter {
310
301
 
311
302
  let now = Instant::now();
312
303
 
313
- let mut entries = self.entries.write().await;
304
+ let mut entries = self.entries.write();
314
305
 
315
306
  let entry = entries
316
307
  .entry(key.to_string())
@@ -436,7 +427,7 @@ impl RateLimiterStore {
436
427
  ) -> Result<Arc<RedisRateLimiter>, RateLimitError> {
437
428
  // First check if this URL is known to fail
438
429
  {
439
- let failed_urls = self.failed_urls.lock().unwrap_or_else(|e| e.into_inner());
430
+ let failed_urls = self.failed_urls.lock();
440
431
  if failed_urls.contains(connection_url) {
441
432
  return Err(RateLimitError::ConnectionTimeout);
442
433
  }
@@ -444,10 +435,7 @@ impl RateLimiterStore {
444
435
 
445
436
  // Then check if we already have a limiter for this URL
446
437
  {
447
- let limiters = self
448
- .redis_limiters
449
- .lock()
450
- .unwrap_or_else(|e| e.into_inner());
438
+ let limiters = self.redis_limiters.lock();
451
439
  if let Some(limiter) = limiters.get(connection_url) {
452
440
  return Ok(limiter.clone());
453
441
  }
@@ -455,7 +443,7 @@ impl RateLimiterStore {
455
443
 
456
444
  // Get a dedicated mutex for this URL or create a new one if it doesn't exist
457
445
  let url_mutex = {
458
- let mut locks = CONNECTION_LOCKS.lock().unwrap_or_else(|e| e.into_inner());
446
+ let mut locks = CONNECTION_LOCKS.lock();
459
447
 
460
448
  // Get or create the mutex for this URL
461
449
  locks
@@ -476,10 +464,7 @@ impl RateLimiterStore {
476
464
 
477
465
  // Check again if another thread created the limiter while we were waiting
478
466
  {
479
- let limiters = self
480
- .redis_limiters
481
- .lock()
482
- .unwrap_or_else(|e| e.into_inner());
467
+ let limiters = self.redis_limiters.lock();
483
468
  if let Some(limiter) = limiters.get(connection_url) {
484
469
  return Ok(limiter.clone());
485
470
  }
@@ -492,10 +477,7 @@ impl RateLimiterStore {
492
477
  let limiter = Arc::new(limiter);
493
478
 
494
479
  // Store it for future use
495
- let mut limiters = self
496
- .redis_limiters
497
- .lock()
498
- .unwrap_or_else(|e| e.into_inner());
480
+ let mut limiters = self.redis_limiters.lock();
499
481
  limiters.insert(connection_url.to_string(), limiter.clone());
500
482
 
501
483
  Ok(limiter)
@@ -503,7 +485,7 @@ impl RateLimiterStore {
503
485
  Err(e) => {
504
486
  tracing::error!("Failed to initialize Redis rate limiter: {}", e);
505
487
  // Cache the failure
506
- let mut failed_urls = self.failed_urls.lock().unwrap_or_else(|e| e.into_inner());
488
+ let mut failed_urls = self.failed_urls.lock();
507
489
  failed_urls.insert(connection_url.to_string());
508
490
  Err(e)
509
491
  }
@@ -19,7 +19,7 @@ use http::{
19
19
  use http_body_util::{combinators::BoxBody, Full};
20
20
  use itsi_error::Result;
21
21
  use parking_lot::{Mutex, RwLock};
22
- use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC};
22
+ use percent_encoding::percent_decode_str;
23
23
  use quick_cache::sync::Cache;
24
24
  use serde::Deserialize;
25
25
  use serde_json::json;
@@ -175,7 +175,7 @@ impl CacheEntry {
175
175
  let mut hasher = Sha256::new();
176
176
  hasher.update(&bytes);
177
177
  let result = hasher.finalize();
178
- general_purpose::STANDARD.encode(result)
178
+ general_purpose::STANDARD.encode(&result[..16])
179
179
  };
180
180
  let headers_ct = get_mime_type(&path);
181
181
  let headers_etag = format!(r#"W/"{etag}""#).parse().unwrap();
@@ -279,7 +279,7 @@ impl StaticFileServer {
279
279
  supported_encodings: &[HeaderValue],
280
280
  ) -> Option<HttpResponse> {
281
281
  let accept: ResponseFormat = request.accept().into();
282
- let resolved = self.resolve(path, abs_path, accept.clone()).await;
282
+ let resolved = self.resolve(path, abs_path, accept).await;
283
283
 
284
284
  Some(match resolved {
285
285
  Ok(ResolvedAsset {
@@ -1188,7 +1188,6 @@ async fn generate_directory_listing(
1188
1188
 
1189
1189
  // Generate JSON entries for directories.
1190
1190
  for (name, metadata) in dirs {
1191
- let encoded = utf8_percent_encode(&name, NON_ALPHANUMERIC).to_string();
1192
1191
  let modified = metadata
1193
1192
  .modified()
1194
1193
  .ok()
@@ -1201,7 +1200,7 @@ async fn generate_directory_listing(
1201
1200
 
1202
1201
  items.push(json!({
1203
1202
  "name": format!("{}/", name),
1204
- "path": format!("{}/", encoded),
1203
+ "path": format!("{}/", name),
1205
1204
  "is_dir": true,
1206
1205
  "size": null,
1207
1206
  "modified": modified,
@@ -1210,7 +1209,6 @@ async fn generate_directory_listing(
1210
1209
 
1211
1210
  // Generate JSON entries for files.
1212
1211
  for (name, metadata) in files {
1213
- let encoded = utf8_percent_encode(&name, NON_ALPHANUMERIC).to_string();
1214
1212
  let file_size = metadata.len();
1215
1213
  let formatted_size = if file_size < 1024 {
1216
1214
  format!("{} B", file_size)
@@ -1234,7 +1232,7 @@ async fn generate_directory_listing(
1234
1232
 
1235
1233
  items.push(json!({
1236
1234
  "name": name,
1237
- "path": encoded,
1235
+ "path": name,
1238
1236
  "is_dir": false,
1239
1237
  "size": formatted_size,
1240
1238
  "modified": modified_str,
@@ -1341,11 +1339,9 @@ async fn generate_directory_listing(
1341
1339
 
1342
1340
  // Generate rows for directories.
1343
1341
  for (name, metadata) in dirs {
1344
- let encoded = utf8_percent_encode(&name, NON_ALPHANUMERIC).to_string();
1345
-
1346
1342
  rows.push_str(&format!(
1347
1343
  r#"<tr><td><a href="{0}/">{1}/</a></td><td class="size">-</td><td class="date">{2}</td></tr>"#,
1348
- encoded,
1344
+ name,
1349
1345
  name,
1350
1346
  metadata.modified().ok().map(|m| DateTime::<Utc>::from(m).format("%Y-%m-%d %H:%M:%S").to_string())
1351
1347
  .unwrap_or_else(|| "-".to_string())
@@ -1355,8 +1351,6 @@ async fn generate_directory_listing(
1355
1351
 
1356
1352
  // Generate rows for files.
1357
1353
  for (name, metadata) in files {
1358
- let encoded = utf8_percent_encode(&name, NON_ALPHANUMERIC).to_string();
1359
-
1360
1354
  let file_size = metadata.len();
1361
1355
  let formatted_size = if file_size < 1024 {
1362
1356
  format!("{} B", file_size)
@@ -1380,7 +1374,7 @@ async fn generate_directory_listing(
1380
1374
 
1381
1375
  rows.push_str(&format!(
1382
1376
  r#"<tr><td><a href="{0}">{1}</a></td><td class="size">{2}</td><td class="date">{3}</td></tr>"#,
1383
- encoded, name, formatted_size, modified_str
1377
+ name, name, formatted_size, modified_str
1384
1378
  ));
1385
1379
  rows.push('\n');
1386
1380
  }
@@ -35,7 +35,7 @@ Pick and choose **just** the features that make sense for you.
35
35
  {{% /details %}}
36
36
 
37
37
  {{% details title="ETag and Cache Control" closed="true" %}}
38
- * Weak and Strong eTag support.
38
+ * Weak and Strong ETag support.
39
39
  * `If-None-Match` and `If-Modified-Since` support.
40
40
  * Automated etag generation for dynamic content (or forwarding of existing `etags` if present)
41
41
  * See <a target="_blank" href="/middleware/etag">etag</a> and <a target="_blank" href="/middleware/cache_control">cache_control</a>
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../../gems/server
3
3
  specs:
4
- itsi-server (0.2.15)
4
+ itsi-server (0.2.16)
5
5
  json (~> 2)
6
6
  prism (~> 1.4)
7
7
  rack (>= 1.6)
@@ -4,7 +4,10 @@ auto_reload_config! # Auto-reload the server configuration each time it changes.
4
4
  # We use not_found_behaviour: "fallthrough" to fall through to Rails static file serving, if
5
5
  # the file isn't found.
6
6
  location "assets*" do
7
- static_assets root_dir: "./public", not_found_behavior: "fallthrough", relative_path: false
7
+ etag type: "strong", algorithm: "sha256"
8
+ static_assets root_dir: "./public", not_found_behavior: "fallthrough", relative_path: false, headers: {
9
+ "cache-control" => "public, max-age=5"
10
+ }
8
11
  end
9
12
 
10
13
  rackup_file "config.ru"
@@ -57,7 +57,7 @@ dependencies = [
57
57
  "regex",
58
58
  "rustc-hash",
59
59
  "shlex",
60
- "syn 2.0.100",
60
+ "syn 2.0.101",
61
61
  ]
62
62
 
63
63
  [[package]]
@@ -74,9 +74,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
74
74
 
75
75
  [[package]]
76
76
  name = "cc"
77
- version = "1.2.19"
77
+ version = "1.2.20"
78
78
  source = "registry+https://github.com/rust-lang/crates.io-index"
79
- checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
79
+ checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a"
80
80
  dependencies = [
81
81
  "shlex",
82
82
  ]
@@ -154,7 +154,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
154
154
  dependencies = [
155
155
  "proc-macro2",
156
156
  "quote",
157
- "syn 2.0.100",
157
+ "syn 2.0.101",
158
158
  "unicode-xid",
159
159
  ]
160
160
 
@@ -166,9 +166,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
166
166
 
167
167
  [[package]]
168
168
  name = "getrandom"
169
- version = "0.2.15"
169
+ version = "0.2.16"
170
170
  source = "registry+https://github.com/rust-lang/crates.io-index"
171
- checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
171
+ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
172
172
  dependencies = [
173
173
  "cfg-if",
174
174
  "libc",
@@ -213,7 +213,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
213
213
 
214
214
  [[package]]
215
215
  name = "itsi-scheduler"
216
- version = "0.2.15"
216
+ version = "0.2.16"
217
217
  dependencies = [
218
218
  "bytes",
219
219
  "derive_more",
@@ -337,7 +337,7 @@ checksum = "5968c820e2960565f647819f5928a42d6e874551cab9d88d75e3e0660d7f71e3"
337
337
  dependencies = [
338
338
  "proc-macro2",
339
339
  "quote",
340
- "syn 2.0.100",
340
+ "syn 2.0.101",
341
341
  ]
342
342
 
343
343
  [[package]]
@@ -507,7 +507,7 @@ dependencies = [
507
507
  "quote",
508
508
  "regex",
509
509
  "shell-words",
510
- "syn 2.0.100",
510
+ "syn 2.0.101",
511
511
  ]
512
512
 
513
513
  [[package]]
@@ -643,7 +643,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
643
643
  dependencies = [
644
644
  "proc-macro2",
645
645
  "quote",
646
- "syn 2.0.100",
646
+ "syn 2.0.101",
647
647
  ]
648
648
 
649
649
  [[package]]
@@ -698,9 +698,9 @@ dependencies = [
698
698
 
699
699
  [[package]]
700
700
  name = "syn"
701
- version = "2.0.100"
701
+ version = "2.0.101"
702
702
  source = "registry+https://github.com/rust-lang/crates.io-index"
703
- checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
703
+ checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
704
704
  dependencies = [
705
705
  "proc-macro2",
706
706
  "quote",
@@ -733,7 +733,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
733
733
  dependencies = [
734
734
  "proc-macro2",
735
735
  "quote",
736
- "syn 2.0.100",
736
+ "syn 2.0.101",
737
737
  ]
738
738
 
739
739
  [[package]]
@@ -744,7 +744,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
744
744
  dependencies = [
745
745
  "proc-macro2",
746
746
  "quote",
747
- "syn 2.0.100",
747
+ "syn 2.0.101",
748
748
  ]
749
749
 
750
750
  [[package]]
@@ -819,7 +819,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
819
819
  dependencies = [
820
820
  "proc-macro2",
821
821
  "quote",
822
- "syn 2.0.100",
822
+ "syn 2.0.101",
823
823
  ]
824
824
 
825
825
  [[package]]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Scheduler
5
- VERSION = "0.2.15"
5
+ VERSION = "0.2.16"
6
6
  end
7
7
  end