itsi 0.2.21.rc1 → 0.2.21
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/.rubocop.yml +20 -0
- data/CHANGELOG.md +3 -0
- data/Cargo.lock +14 -14
- data/Dockerfile +9 -0
- data/crates/itsi_acme/src/caches/no.rs +1 -1
- data/crates/itsi_acme/src/caches/test.rs +3 -3
- data/crates/itsi_acme/src/config.rs +6 -6
- data/crates/itsi_acme/src/lib.rs +1 -1
- data/crates/itsi_error/Cargo.toml +1 -1
- data/crates/itsi_error/src/lib.rs +32 -15
- data/crates/itsi_rb_helpers/Cargo.toml +2 -2
- data/crates/itsi_rb_helpers/src/heap_value.rs +4 -4
- data/crates/itsi_rb_helpers/src/lib.rs +9 -5
- data/crates/itsi_scheduler/Cargo.toml +3 -3
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
- data/crates/itsi_server/Cargo.toml +3 -3
- data/crates/itsi_server/src/lib.rs +3 -2
- data/crates/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +10 -3
- data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +10 -6
- data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +7 -5
- data/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +2 -2
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +10 -7
- data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +13 -5
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +6 -6
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +15 -11
- 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/middleware.rs +1 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +1 -3
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +8 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +2 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +1 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +17 -7
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +90 -21
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +12 -12
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +4 -3
- 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 +38 -13
- data/crates/itsi_tracing/src/lib.rs +3 -3
- data/gems/scheduler/Cargo.lock +3997 -541
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/server/Cargo.lock +40 -13
- 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 +9 -5
|
@@ -63,7 +63,7 @@ pub struct ResponseInner {
|
|
|
63
63
|
|
|
64
64
|
#[derive(Debug)]
|
|
65
65
|
pub enum ResponseFrame {
|
|
66
|
-
HttpResponse(HttpResponse),
|
|
66
|
+
HttpResponse(Box<HttpResponse>),
|
|
67
67
|
HijackedResponse(ItsiHttpResponse),
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -225,7 +225,9 @@ impl ItsiHttpResponse {
|
|
|
225
225
|
if let Some(mut response) = self.response.write().take() {
|
|
226
226
|
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
|
227
227
|
if let Some(sender) = self.response_sender.write().take() {
|
|
228
|
-
sender
|
|
228
|
+
sender
|
|
229
|
+
.send(ResponseFrame::HttpResponse(Box::new(response)))
|
|
230
|
+
.ok();
|
|
229
231
|
}
|
|
230
232
|
}
|
|
231
233
|
}
|
|
@@ -235,7 +237,9 @@ impl ItsiHttpResponse {
|
|
|
235
237
|
if let Some(mut response) = self.response.write().take() {
|
|
236
238
|
*response.status_mut() = StatusCode::SERVICE_UNAVAILABLE;
|
|
237
239
|
if let Some(sender) = self.response_sender.write().take() {
|
|
238
|
-
sender
|
|
240
|
+
sender
|
|
241
|
+
.send(ResponseFrame::HttpResponse(Box::new(response)))
|
|
242
|
+
.ok();
|
|
239
243
|
}
|
|
240
244
|
}
|
|
241
245
|
}
|
|
@@ -253,7 +257,9 @@ impl ItsiHttpResponse {
|
|
|
253
257
|
*response.body_mut() = HttpBody::stream(buffered);
|
|
254
258
|
self.frame_writer.write().replace(writer);
|
|
255
259
|
if let Some(sender) = self.response_sender.write().take() {
|
|
256
|
-
sender
|
|
260
|
+
sender
|
|
261
|
+
.send(ResponseFrame::HttpResponse(Box::new(response)))
|
|
262
|
+
.ok();
|
|
257
263
|
}
|
|
258
264
|
} else {
|
|
259
265
|
info!("No response!");
|
|
@@ -280,7 +286,9 @@ impl ItsiHttpResponse {
|
|
|
280
286
|
*response.body_mut() = HttpBody::full(frame);
|
|
281
287
|
}
|
|
282
288
|
if let Some(sender) = self.response_sender.write().take() {
|
|
283
|
-
sender
|
|
289
|
+
sender
|
|
290
|
+
.send(ResponseFrame::HttpResponse(Box::new(response)))
|
|
291
|
+
.ok();
|
|
284
292
|
}
|
|
285
293
|
}
|
|
286
294
|
|
|
@@ -110,7 +110,7 @@ pub fn send_watcher_command(fd: &OwnedFd, cmd: WatcherCommand) -> Result<()> {
|
|
|
110
110
|
match write(fd, &buf) {
|
|
111
111
|
Ok(_) => Ok(()),
|
|
112
112
|
Err(e) => Err(magnus::Error::new(
|
|
113
|
-
magnus::
|
|
113
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
114
114
|
format!("Failed to send command to watcher: {}", e),
|
|
115
115
|
)),
|
|
116
116
|
}
|
|
@@ -122,14 +122,14 @@ pub fn watch_groups(
|
|
|
122
122
|
// Create bidirectional pipes for communication
|
|
123
123
|
let (parent_read_fd, child_write_fd): (OwnedFd, OwnedFd) = pipe().map_err(|e| {
|
|
124
124
|
magnus::Error::new(
|
|
125
|
-
magnus::
|
|
125
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
126
126
|
format!("Failed to create parent read pipe: {}", e),
|
|
127
127
|
)
|
|
128
128
|
})?;
|
|
129
129
|
|
|
130
130
|
let (child_read_fd, parent_write_fd): (OwnedFd, OwnedFd) = pipe().map_err(|e| {
|
|
131
131
|
magnus::Error::new(
|
|
132
|
-
magnus::
|
|
132
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
133
133
|
format!("Failed to create child read pipe: {}", e),
|
|
134
134
|
)
|
|
135
135
|
})?;
|
|
@@ -137,7 +137,7 @@ pub fn watch_groups(
|
|
|
137
137
|
let fork_result = unsafe {
|
|
138
138
|
fork().map_err(|e| {
|
|
139
139
|
magnus::Error::new(
|
|
140
|
-
magnus::
|
|
140
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
141
141
|
format!("Failed to fork file watcher: {}", e),
|
|
142
142
|
)
|
|
143
143
|
})
|
|
@@ -194,7 +194,7 @@ pub fn watch_groups(
|
|
|
194
194
|
|
|
195
195
|
let glob = Glob::new(&remaining_pattern).map_err(|e| {
|
|
196
196
|
magnus::Error::new(
|
|
197
|
-
magnus::
|
|
197
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
198
198
|
format!(
|
|
199
199
|
"Failed to create watch glob for pattern '{}': {}",
|
|
200
200
|
remaining_pattern, e
|
|
@@ -203,7 +203,7 @@ pub fn watch_groups(
|
|
|
203
203
|
})?;
|
|
204
204
|
let glob_set = GlobSetBuilder::new().add(glob).build().map_err(|e| {
|
|
205
205
|
magnus::Error::new(
|
|
206
|
-
magnus::
|
|
206
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
207
207
|
format!("Failed to create watch glob set: {}", e),
|
|
208
208
|
)
|
|
209
209
|
})?;
|
|
@@ -14,7 +14,7 @@ use magnus::{
|
|
|
14
14
|
block::Proc,
|
|
15
15
|
error::Result,
|
|
16
16
|
value::{LazyId, ReprValue},
|
|
17
|
-
RArray, RHash, Ruby,
|
|
17
|
+
RArray, RHash, Ruby, TryConvert, Value,
|
|
18
18
|
};
|
|
19
19
|
use nix::{
|
|
20
20
|
fcntl::{fcntl, FcntlArg, FdFlag},
|
|
@@ -161,14 +161,14 @@ impl ServerParams {
|
|
|
161
161
|
Vec::<String>::try_convert(error_lines.unwrap().as_value())?;
|
|
162
162
|
ItsiServerConfig::print_config_errors(errors);
|
|
163
163
|
return Err(magnus::Error::new(
|
|
164
|
-
magnus::
|
|
164
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
165
165
|
"Failed to set middleware",
|
|
166
166
|
));
|
|
167
167
|
}
|
|
168
168
|
let middleware = MiddlewareSet::new(routes_raw)?;
|
|
169
169
|
self.middleware.set(middleware).map_err(|_| {
|
|
170
170
|
magnus::Error::new(
|
|
171
|
-
magnus::
|
|
171
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
172
172
|
"Failed to set middleware",
|
|
173
173
|
)
|
|
174
174
|
})?;
|
|
@@ -361,7 +361,7 @@ impl ServerParams {
|
|
|
361
361
|
let bind_to_fd_map: HashMap<String, i32> = serde_json::from_str(preexisting_listeners)
|
|
362
362
|
.map_err(|e| {
|
|
363
363
|
magnus::Error::new(
|
|
364
|
-
magnus::
|
|
364
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
365
365
|
format!("Invalid listener info: {}", e),
|
|
366
366
|
)
|
|
367
367
|
})?;
|
|
@@ -393,7 +393,10 @@ impl ServerParams {
|
|
|
393
393
|
.iter()
|
|
394
394
|
.map(|listener| {
|
|
395
395
|
listener.handover().map_err(|e| {
|
|
396
|
-
magnus::Error::new(
|
|
396
|
+
magnus::Error::new(
|
|
397
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
398
|
+
e.to_string(),
|
|
399
|
+
)
|
|
397
400
|
})
|
|
398
401
|
})
|
|
399
402
|
.collect::<Result<HashMap<String, i32>>>()?;
|
|
@@ -419,7 +422,8 @@ impl ItsiServerConfig {
|
|
|
419
422
|
itsi_config_proc.clone(),
|
|
420
423
|
) {
|
|
421
424
|
Ok(server_params) => {
|
|
422
|
-
cli_params
|
|
425
|
+
cli_params
|
|
426
|
+
.delete::<_, Value>(magnus::Ruby::get().unwrap().to_symbol("listeners"))?;
|
|
423
427
|
|
|
424
428
|
let watcher_fd = if let Some(watchers) = server_params.notify_watchers.clone() {
|
|
425
429
|
file_watcher::watch_groups(watchers)?
|
|
@@ -436,7 +440,7 @@ impl ItsiServerConfig {
|
|
|
436
440
|
})
|
|
437
441
|
}
|
|
438
442
|
Err(err) => Err(magnus::Error::new(
|
|
439
|
-
magnus::
|
|
443
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
440
444
|
format!("Error loading initial configuration {:?}", err),
|
|
441
445
|
)),
|
|
442
446
|
}
|
|
@@ -493,7 +497,7 @@ impl ItsiServerConfig {
|
|
|
493
497
|
if !errors.is_empty() {
|
|
494
498
|
Self::print_config_errors(errors);
|
|
495
499
|
return Err(magnus::Error::new(
|
|
496
|
-
magnus::
|
|
500
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
497
501
|
"Invalid server config",
|
|
498
502
|
));
|
|
499
503
|
}
|
|
@@ -567,13 +571,13 @@ impl ItsiServerConfig {
|
|
|
567
571
|
.map(|(str, fd)| {
|
|
568
572
|
let dupped_fd = dup(*fd).map_err(|errno| {
|
|
569
573
|
magnus::Error::new(
|
|
570
|
-
magnus::
|
|
574
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
571
575
|
format!("Errno {} while trying to dup {}", errno, fd),
|
|
572
576
|
)
|
|
573
577
|
})?;
|
|
574
578
|
Self::clear_cloexec(dupped_fd).map_err(|e| {
|
|
575
579
|
magnus::Error::new(
|
|
576
|
-
magnus::
|
|
580
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
577
581
|
format!("Failed to clear cloexec flag for fd {}: {}", dupped_fd, e),
|
|
578
582
|
)
|
|
579
583
|
})?;
|
|
@@ -629,7 +633,7 @@ impl ItsiServerConfig {
|
|
|
629
633
|
serde_json::to_string(&self.server_params.read().listener_info.lock().clone())
|
|
630
634
|
.map_err(|e| {
|
|
631
635
|
magnus::Error::new(
|
|
632
|
-
magnus::
|
|
636
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
633
637
|
format!("Invalid listener info: {}", e),
|
|
634
638
|
)
|
|
635
639
|
})?;
|
|
@@ -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::Ruby::get().unwrap().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::Ruby::get().unwrap().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
|
}
|
|
@@ -293,9 +293,7 @@ impl MiddlewareLayer for Compression {
|
|
|
293
293
|
};
|
|
294
294
|
HttpBody::full(Bytes::from(compressed_bytes))
|
|
295
295
|
} else {
|
|
296
|
-
let stream = body
|
|
297
|
-
.into_data_stream()
|
|
298
|
-
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e));
|
|
296
|
+
let stream = body.into_data_stream().map_err(std::io::Error::other);
|
|
299
297
|
let async_read_fut = StreamReader::new(stream);
|
|
300
298
|
let reader = BufReader::new(async_read_fut);
|
|
301
299
|
match compression_method {
|
|
@@ -43,9 +43,9 @@ 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;
|
|
47
46
|
use magnus::rb_sys::AsRawValue;
|
|
48
47
|
use magnus::Value;
|
|
48
|
+
use magnus::{error::Result, Ruby};
|
|
49
49
|
pub use max_body::MaxBody;
|
|
50
50
|
pub use proxy::Proxy;
|
|
51
51
|
pub use rate_limit::RateLimit;
|
|
@@ -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::Ruby::get().unwrap().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
|
}
|
|
@@ -280,14 +280,14 @@ impl MiddlewareLayer for Proxy {
|
|
|
280
280
|
.build()
|
|
281
281
|
.map_err(|e| {
|
|
282
282
|
magnus::Error::new(
|
|
283
|
-
magnus::
|
|
283
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
284
284
|
format!("Failed to build Reqwest client: {}", e),
|
|
285
285
|
)
|
|
286
286
|
})?,
|
|
287
287
|
)
|
|
288
288
|
.map_err(|_e| {
|
|
289
289
|
magnus::Error::new(
|
|
290
|
-
magnus::
|
|
290
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
291
291
|
"Failed to save resolver backends",
|
|
292
292
|
)
|
|
293
293
|
})?;
|
|
@@ -42,7 +42,7 @@ impl Redirect {
|
|
|
42
42
|
*response.status_mut() = self.redirect_type.status_code();
|
|
43
43
|
let destination = self.to.rewrite_request(req, context).parse().map_err(|e| {
|
|
44
44
|
magnus::Error::new(
|
|
45
|
-
magnus::
|
|
45
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
46
46
|
format!("Invalid Rewrite String: {:?}: {}", self.to, e),
|
|
47
47
|
)
|
|
48
48
|
})?;
|
|
@@ -8,7 +8,7 @@ use async_trait::async_trait;
|
|
|
8
8
|
use derive_more::Debug;
|
|
9
9
|
use either::Either;
|
|
10
10
|
use itsi_rb_helpers::{HeapVal, HeapValue};
|
|
11
|
-
use magnus::{block::Proc, error::Result, value::ReprValue
|
|
11
|
+
use magnus::{block::Proc, error::Result, value::ReprValue};
|
|
12
12
|
use regex::Regex;
|
|
13
13
|
use std::str::FromStr;
|
|
14
14
|
use std::sync::atomic::Ordering;
|
|
@@ -44,23 +44,33 @@ impl FromStr for RequestType {
|
|
|
44
44
|
|
|
45
45
|
impl RubyApp {
|
|
46
46
|
pub fn from_value(params: HeapVal) -> magnus::error::Result<Arc<Self>> {
|
|
47
|
-
let app = params
|
|
47
|
+
let app = params
|
|
48
|
+
.funcall::<_, _, Proc>(magnus::Ruby::get().unwrap().to_symbol("[]"), ("app_proc",))?;
|
|
48
49
|
let sendfile = params
|
|
49
|
-
.funcall::<_, _, bool>(
|
|
50
|
+
.funcall::<_, _, bool>(magnus::Ruby::get().unwrap().to_symbol("[]"), ("sendfile",))
|
|
50
51
|
.unwrap_or(true);
|
|
51
52
|
let nonblocking = params
|
|
52
|
-
.funcall::<_, _, bool>(
|
|
53
|
+
.funcall::<_, _, bool>(
|
|
54
|
+
magnus::Ruby::get().unwrap().to_symbol("[]"),
|
|
55
|
+
("nonblocking",),
|
|
56
|
+
)
|
|
53
57
|
.unwrap_or(false);
|
|
54
58
|
let base_path_src = params
|
|
55
|
-
.funcall::<_, _, String>(
|
|
59
|
+
.funcall::<_, _, String>(magnus::Ruby::get().unwrap().to_symbol("[]"), ("base_path",))
|
|
56
60
|
.unwrap_or("".to_owned());
|
|
57
61
|
let script_name = params
|
|
58
|
-
.funcall::<_, _, Option<String>>(
|
|
62
|
+
.funcall::<_, _, Option<String>>(
|
|
63
|
+
magnus::Ruby::get().unwrap().to_symbol("[]"),
|
|
64
|
+
("script_name",),
|
|
65
|
+
)
|
|
59
66
|
.unwrap_or(None);
|
|
60
67
|
let base_path = Regex::new(&base_path_src).unwrap();
|
|
61
68
|
|
|
62
69
|
let request_type: RequestType = params
|
|
63
|
-
.funcall::<_, _, String>(
|
|
70
|
+
.funcall::<_, _, String>(
|
|
71
|
+
magnus::Ruby::get().unwrap().to_symbol("[]"),
|
|
72
|
+
("request_type",),
|
|
73
|
+
)
|
|
64
74
|
.unwrap_or("http".to_string())
|
|
65
75
|
.parse()
|
|
66
76
|
.unwrap_or(RequestType::Http);
|
|
@@ -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
|
};
|