itsi-scheduler 0.2.22-aarch64-linux
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 +7 -0
- data/.rubocop.yml +8 -0
- data/Cargo.lock +997 -0
- data/Cargo.toml +7 -0
- data/Rakefile +39 -0
- data/ext/itsi_acme/Cargo.toml +86 -0
- data/ext/itsi_acme/examples/high_level.rs +63 -0
- data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
- data/ext/itsi_acme/examples/low_level.rs +87 -0
- data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
- data/ext/itsi_acme/src/acceptor.rs +81 -0
- data/ext/itsi_acme/src/acme.rs +354 -0
- data/ext/itsi_acme/src/axum.rs +86 -0
- data/ext/itsi_acme/src/cache.rs +39 -0
- data/ext/itsi_acme/src/caches/boxed.rs +80 -0
- data/ext/itsi_acme/src/caches/composite.rs +69 -0
- data/ext/itsi_acme/src/caches/dir.rs +106 -0
- data/ext/itsi_acme/src/caches/mod.rs +11 -0
- data/ext/itsi_acme/src/caches/no.rs +78 -0
- data/ext/itsi_acme/src/caches/test.rs +136 -0
- data/ext/itsi_acme/src/config.rs +172 -0
- data/ext/itsi_acme/src/https_helper.rs +69 -0
- data/ext/itsi_acme/src/incoming.rs +142 -0
- data/ext/itsi_acme/src/jose.rs +161 -0
- data/ext/itsi_acme/src/lib.rs +142 -0
- data/ext/itsi_acme/src/resolver.rs +59 -0
- data/ext/itsi_acme/src/state.rs +424 -0
- data/ext/itsi_error/Cargo.lock +368 -0
- data/ext/itsi_error/Cargo.toml +12 -0
- data/ext/itsi_error/src/lib.rs +140 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.lock +355 -0
- data/ext/itsi_rb_helpers/Cargo.toml +11 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +232 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/extconf.rb +11 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +320 -0
- data/ext/itsi_scheduler/src/lib.rs +39 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +94 -0
- data/ext/itsi_server/src/default_responses/mod.rs +14 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +154 -0
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +116 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +149 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +346 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +265 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +399 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +447 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +545 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +650 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +102 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
- data/ext/itsi_server/src/server/binds/bind.rs +204 -0
- data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/binds/listener.rs +485 -0
- data/ext/itsi_server/src/server/binds/mod.rs +4 -0
- data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/binds/tls.rs +278 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/frame_stream.rs +143 -0
- data/ext/itsi_server/src/server/http_message_types.rs +230 -0
- data/ext/itsi_server/src/server/io_stream.rs +128 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +170 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +63 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +94 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +343 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +151 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +329 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +193 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +64 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +188 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +168 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +183 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +209 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +133 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +122 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +407 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +155 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +54 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +54 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +51 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +138 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +269 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +62 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +218 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +31 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +381 -0
- data/ext/itsi_server/src/server/mod.rs +14 -0
- data/ext/itsi_server/src/server/process_worker.rs +247 -0
- data/ext/itsi_server/src/server/redirect_type.rs +26 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +411 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +31 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +449 -0
- data/ext/itsi_server/src/server/signal.rs +129 -0
- data/ext/itsi_server/src/server/size_limited_incoming.rs +107 -0
- data/ext/itsi_server/src/server/thread_worker.rs +504 -0
- data/ext/itsi_server/src/services/cache_store.rs +74 -0
- data/ext/itsi_server/src/services/itsi_http_service.rs +270 -0
- data/ext/itsi_server/src/services/mime_types.rs +2896 -0
- data/ext/itsi_server/src/services/mod.rs +6 -0
- data/ext/itsi_server/src/services/password_hasher.rs +89 -0
- data/ext/itsi_server/src/services/rate_limiter.rs +609 -0
- data/ext/itsi_server/src/services/static_file_server.rs +1400 -0
- data/ext/itsi_tracing/Cargo.lock +274 -0
- data/ext/itsi_tracing/Cargo.toml +17 -0
- data/ext/itsi_tracing/src/lib.rs +370 -0
- data/itsi-scheduler-100.png +0 -0
- data/lib/itsi/schedule_refinement.rb +96 -0
- data/lib/itsi/scheduler/3.1/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.2/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.3/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.4/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/4.0/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/native_extension.rb +34 -0
- data/lib/itsi/scheduler/version.rb +7 -0
- data/lib/itsi/scheduler.rb +153 -0
- data/vendor/rb-sys-build/.cargo-ok +1 -0
- data/vendor/rb-sys-build/.cargo_vcs_info.json +6 -0
- data/vendor/rb-sys-build/Cargo.lock +294 -0
- data/vendor/rb-sys-build/Cargo.toml +71 -0
- data/vendor/rb-sys-build/Cargo.toml.orig +32 -0
- data/vendor/rb-sys-build/LICENSE-APACHE +190 -0
- data/vendor/rb-sys-build/LICENSE-MIT +21 -0
- data/vendor/rb-sys-build/src/bindings/sanitizer.rs +185 -0
- data/vendor/rb-sys-build/src/bindings/stable_api.rs +247 -0
- data/vendor/rb-sys-build/src/bindings/wrapper.h +71 -0
- data/vendor/rb-sys-build/src/bindings.rs +280 -0
- data/vendor/rb-sys-build/src/cc.rs +421 -0
- data/vendor/rb-sys-build/src/lib.rs +12 -0
- data/vendor/rb-sys-build/src/rb_config/flags.rs +101 -0
- data/vendor/rb-sys-build/src/rb_config/library.rs +132 -0
- data/vendor/rb-sys-build/src/rb_config/search_path.rs +57 -0
- data/vendor/rb-sys-build/src/rb_config.rs +906 -0
- data/vendor/rb-sys-build/src/utils.rs +53 -0
- metadata +210 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
|
|
2
|
+
use crate::{
|
|
3
|
+
server::{
|
|
4
|
+
http_message_types::{HttpBody, HttpRequest, HttpResponse},
|
|
5
|
+
redirect_type::RedirectType,
|
|
6
|
+
},
|
|
7
|
+
services::itsi_http_service::HttpRequestContext,
|
|
8
|
+
};
|
|
9
|
+
use async_trait::async_trait;
|
|
10
|
+
use either::Either;
|
|
11
|
+
use http::Response;
|
|
12
|
+
use magnus::error::Result;
|
|
13
|
+
use serde::Deserialize;
|
|
14
|
+
use tracing::debug;
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
17
|
+
pub struct Redirect {
|
|
18
|
+
pub to: StringRewrite,
|
|
19
|
+
#[serde(default)]
|
|
20
|
+
#[serde(rename(deserialize = "type"))]
|
|
21
|
+
pub redirect_type: RedirectType,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[async_trait]
|
|
25
|
+
impl MiddlewareLayer for Redirect {
|
|
26
|
+
async fn before(
|
|
27
|
+
&self,
|
|
28
|
+
req: HttpRequest,
|
|
29
|
+
context: &mut HttpRequestContext,
|
|
30
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
|
31
|
+
Ok(Either::Right(self.redirect_response(&req, context)?))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl Redirect {
|
|
36
|
+
pub fn redirect_response(
|
|
37
|
+
&self,
|
|
38
|
+
req: &HttpRequest,
|
|
39
|
+
context: &mut HttpRequestContext,
|
|
40
|
+
) -> Result<HttpResponse> {
|
|
41
|
+
let mut response = Response::new(HttpBody::empty());
|
|
42
|
+
*response.status_mut() = self.redirect_type.status_code();
|
|
43
|
+
let destination = self.to.rewrite_request(req, context).parse().map_err(|e| {
|
|
44
|
+
magnus::Error::new(
|
|
45
|
+
magnus::Ruby::get().unwrap().exception_standard_error(),
|
|
46
|
+
format!("Invalid Rewrite String: {:?}: {}", self.to, e),
|
|
47
|
+
)
|
|
48
|
+
})?;
|
|
49
|
+
debug!(target: "middleware::redirect", "Redirecting to {:?}", destination);
|
|
50
|
+
response.headers_mut().append("Location", destination);
|
|
51
|
+
Ok(response)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
impl FromValue for Redirect {}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
3
|
+
use crate::{
|
|
4
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
|
5
|
+
services::itsi_http_service::HttpRequestContext,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
use super::{FromValue, MiddlewareLayer, StringRewrite};
|
|
9
|
+
use async_trait::async_trait;
|
|
10
|
+
use either::Either;
|
|
11
|
+
use http::HeaderName;
|
|
12
|
+
use magnus::error::Result;
|
|
13
|
+
use serde::Deserialize;
|
|
14
|
+
|
|
15
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
16
|
+
pub struct RequestHeaders {
|
|
17
|
+
pub additions: HashMap<String, Vec<StringRewrite>>,
|
|
18
|
+
pub removals: Vec<String>,
|
|
19
|
+
}
|
|
20
|
+
#[async_trait]
|
|
21
|
+
impl MiddlewareLayer for RequestHeaders {
|
|
22
|
+
async fn before(
|
|
23
|
+
&self,
|
|
24
|
+
mut req: HttpRequest,
|
|
25
|
+
context: &mut HttpRequestContext,
|
|
26
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
|
27
|
+
let mut headers_to_add = Vec::new();
|
|
28
|
+
|
|
29
|
+
for (header_name, header_values) in &self.additions {
|
|
30
|
+
if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
|
|
31
|
+
for header_value in header_values {
|
|
32
|
+
if let Ok(parsed_header_value) =
|
|
33
|
+
header_value.rewrite_request(&req, context).parse()
|
|
34
|
+
{
|
|
35
|
+
headers_to_add.push((parsed_header_name.clone(), parsed_header_value));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let headers = req.headers_mut();
|
|
42
|
+
|
|
43
|
+
for removal in &self.removals {
|
|
44
|
+
headers.remove(removal);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (name, value) in headers_to_add {
|
|
48
|
+
headers.append(name, value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Ok(Either::Left(req))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
impl FromValue for RequestHeaders {}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
3
|
+
use super::{FromValue, MiddlewareLayer, StringRewrite};
|
|
4
|
+
use crate::{
|
|
5
|
+
server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
|
|
6
|
+
};
|
|
7
|
+
use async_trait::async_trait;
|
|
8
|
+
use http::HeaderName;
|
|
9
|
+
use serde::Deserialize;
|
|
10
|
+
|
|
11
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
12
|
+
pub struct ResponseHeaders {
|
|
13
|
+
pub additions: HashMap<String, Vec<StringRewrite>>,
|
|
14
|
+
pub removals: Vec<String>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[async_trait]
|
|
18
|
+
impl MiddlewareLayer for ResponseHeaders {
|
|
19
|
+
async fn after(
|
|
20
|
+
&self,
|
|
21
|
+
mut resp: HttpResponse,
|
|
22
|
+
context: &mut HttpRequestContext,
|
|
23
|
+
) -> HttpResponse {
|
|
24
|
+
let mut headers_to_add = Vec::new();
|
|
25
|
+
|
|
26
|
+
for (header_name, header_values) in &self.additions {
|
|
27
|
+
if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
|
|
28
|
+
for header_value in header_values {
|
|
29
|
+
if let Ok(parsed_header_value) =
|
|
30
|
+
header_value.rewrite_response(&resp, context).parse()
|
|
31
|
+
{
|
|
32
|
+
headers_to_add.push((parsed_header_name.clone(), parsed_header_value));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let headers = resp.headers_mut();
|
|
39
|
+
|
|
40
|
+
for removal in &self.removals {
|
|
41
|
+
headers.remove(removal);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (name, value) in headers_to_add {
|
|
45
|
+
headers.append(name, value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
resp
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
impl FromValue for ResponseHeaders {}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
use super::MiddlewareLayer;
|
|
2
|
+
use crate::ruby_types::itsi_grpc_call::ItsiGrpcCall;
|
|
3
|
+
use crate::ruby_types::itsi_http_request::ItsiHttpRequest;
|
|
4
|
+
use crate::server::http_message_types::{HttpRequest, HttpResponse};
|
|
5
|
+
use crate::services::itsi_http_service::HttpRequestContext;
|
|
6
|
+
use crate::services::static_file_server::ROOT_STATIC_FILE_SERVER;
|
|
7
|
+
use async_trait::async_trait;
|
|
8
|
+
use derive_more::Debug;
|
|
9
|
+
use either::Either;
|
|
10
|
+
use itsi_rb_helpers::{HeapVal, HeapValue};
|
|
11
|
+
use magnus::{block::Proc, error::Result, value::ReprValue};
|
|
12
|
+
use regex::Regex;
|
|
13
|
+
use std::str::FromStr;
|
|
14
|
+
use std::sync::atomic::Ordering;
|
|
15
|
+
use std::sync::Arc;
|
|
16
|
+
|
|
17
|
+
#[derive(Debug)]
|
|
18
|
+
pub struct RubyApp {
|
|
19
|
+
app: Arc<HeapValue<Proc>>,
|
|
20
|
+
request_type: RequestType,
|
|
21
|
+
script_name: Option<String>,
|
|
22
|
+
sendfile: bool,
|
|
23
|
+
nonblocking: bool,
|
|
24
|
+
base_path: Regex,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[derive(Debug)]
|
|
28
|
+
pub enum RequestType {
|
|
29
|
+
Http,
|
|
30
|
+
Grpc,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl FromStr for RequestType {
|
|
34
|
+
type Err = &'static str;
|
|
35
|
+
|
|
36
|
+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
|
37
|
+
match s {
|
|
38
|
+
"http" => Ok(RequestType::Http),
|
|
39
|
+
"grpc" => Ok(RequestType::Grpc),
|
|
40
|
+
_ => Err("Invalid request type"),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
impl RubyApp {
|
|
46
|
+
pub fn from_value(params: HeapVal) -> magnus::error::Result<Arc<Self>> {
|
|
47
|
+
let app = params
|
|
48
|
+
.funcall::<_, _, Proc>(magnus::Ruby::get().unwrap().to_symbol("[]"), ("app_proc",))?;
|
|
49
|
+
let sendfile = params
|
|
50
|
+
.funcall::<_, _, bool>(magnus::Ruby::get().unwrap().to_symbol("[]"), ("sendfile",))
|
|
51
|
+
.unwrap_or(true);
|
|
52
|
+
let nonblocking = params
|
|
53
|
+
.funcall::<_, _, bool>(
|
|
54
|
+
magnus::Ruby::get().unwrap().to_symbol("[]"),
|
|
55
|
+
("nonblocking",),
|
|
56
|
+
)
|
|
57
|
+
.unwrap_or(false);
|
|
58
|
+
let base_path_src = params
|
|
59
|
+
.funcall::<_, _, String>(magnus::Ruby::get().unwrap().to_symbol("[]"), ("base_path",))
|
|
60
|
+
.unwrap_or("".to_owned());
|
|
61
|
+
let script_name = params
|
|
62
|
+
.funcall::<_, _, Option<String>>(
|
|
63
|
+
magnus::Ruby::get().unwrap().to_symbol("[]"),
|
|
64
|
+
("script_name",),
|
|
65
|
+
)
|
|
66
|
+
.unwrap_or(None);
|
|
67
|
+
let base_path = Regex::new(&base_path_src).unwrap();
|
|
68
|
+
|
|
69
|
+
let request_type: RequestType = params
|
|
70
|
+
.funcall::<_, _, String>(
|
|
71
|
+
magnus::Ruby::get().unwrap().to_symbol("[]"),
|
|
72
|
+
("request_type",),
|
|
73
|
+
)
|
|
74
|
+
.unwrap_or("http".to_string())
|
|
75
|
+
.parse()
|
|
76
|
+
.unwrap_or(RequestType::Http);
|
|
77
|
+
|
|
78
|
+
Ok(Arc::new(RubyApp {
|
|
79
|
+
app: Arc::new(app.into()),
|
|
80
|
+
sendfile,
|
|
81
|
+
nonblocking,
|
|
82
|
+
script_name,
|
|
83
|
+
request_type,
|
|
84
|
+
base_path,
|
|
85
|
+
}))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[async_trait]
|
|
90
|
+
impl MiddlewareLayer for RubyApp {
|
|
91
|
+
async fn before(
|
|
92
|
+
&self,
|
|
93
|
+
req: HttpRequest,
|
|
94
|
+
context: &mut HttpRequestContext,
|
|
95
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
|
96
|
+
context.is_ruby_request.store(true, Ordering::SeqCst);
|
|
97
|
+
match self.request_type {
|
|
98
|
+
RequestType::Http => {
|
|
99
|
+
let uri = req.uri().path();
|
|
100
|
+
let script_name = self.script_name.clone().unwrap_or_else(|| {
|
|
101
|
+
self.base_path
|
|
102
|
+
.captures(uri)
|
|
103
|
+
.and_then(|caps| caps.name("base_path"))
|
|
104
|
+
.map(|m| m.as_str())
|
|
105
|
+
.unwrap_or("/")
|
|
106
|
+
.to_owned()
|
|
107
|
+
});
|
|
108
|
+
ItsiHttpRequest::process_request(
|
|
109
|
+
self.app.clone(),
|
|
110
|
+
req,
|
|
111
|
+
context,
|
|
112
|
+
script_name,
|
|
113
|
+
self.nonblocking,
|
|
114
|
+
)
|
|
115
|
+
.await
|
|
116
|
+
.map_err(|e| e.into())
|
|
117
|
+
.map(Either::Right)
|
|
118
|
+
}
|
|
119
|
+
RequestType::Grpc => {
|
|
120
|
+
ItsiGrpcCall::process_request(self.app.clone(), req, context, self.nonblocking)
|
|
121
|
+
.await
|
|
122
|
+
.map_err(|e| e.into())
|
|
123
|
+
.map(Either::Right)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
|
|
129
|
+
if self.sendfile {
|
|
130
|
+
if let Some(sendfile_header) = resp.headers().get("X-Sendfile") {
|
|
131
|
+
return ROOT_STATIC_FILE_SERVER
|
|
132
|
+
.serve_single_abs(sendfile_header.to_str().unwrap(), context.accept, &[])
|
|
133
|
+
.await;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
resp
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
use super::{FromValue, MiddlewareLayer};
|
|
2
|
+
use crate::{
|
|
3
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
|
4
|
+
services::{
|
|
5
|
+
itsi_http_service::HttpRequestContext,
|
|
6
|
+
static_file_server::{
|
|
7
|
+
NotFoundBehavior, ServeRange, StaticFileServer, StaticFileServerConfig,
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
use async_trait::async_trait;
|
|
12
|
+
use either::Either;
|
|
13
|
+
use http::{
|
|
14
|
+
header::{IF_MODIFIED_SINCE, RANGE},
|
|
15
|
+
HeaderMap, HeaderValue, Method,
|
|
16
|
+
};
|
|
17
|
+
use itsi_error::ItsiError;
|
|
18
|
+
use magnus::error::Result;
|
|
19
|
+
use quick_cache::sync::Cache;
|
|
20
|
+
use regex::Regex;
|
|
21
|
+
use serde::Deserialize;
|
|
22
|
+
use std::{
|
|
23
|
+
collections::HashMap,
|
|
24
|
+
path::PathBuf,
|
|
25
|
+
sync::{Arc, OnceLock},
|
|
26
|
+
time::Duration,
|
|
27
|
+
};
|
|
28
|
+
use tracing::debug;
|
|
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
|
+
|
|
83
|
+
#[derive(Debug, Deserialize)]
|
|
84
|
+
pub struct StaticAssets {
|
|
85
|
+
pub root_dir: PathBuf,
|
|
86
|
+
pub not_found_behavior: NotFoundBehavior,
|
|
87
|
+
pub auto_index: bool,
|
|
88
|
+
pub try_html_extension: bool,
|
|
89
|
+
pub max_file_size_in_memory: u64,
|
|
90
|
+
pub max_files_in_memory: u64,
|
|
91
|
+
pub file_check_interval: u64,
|
|
92
|
+
pub headers: Option<HashMap<String, String>>,
|
|
93
|
+
pub allowed_extensions: Vec<String>,
|
|
94
|
+
pub relative_path: bool,
|
|
95
|
+
pub serve_hidden_files: bool,
|
|
96
|
+
pub base_path: String,
|
|
97
|
+
#[serde(skip)]
|
|
98
|
+
pub base_path_regex: OnceLock<Regex>,
|
|
99
|
+
#[serde(skip)]
|
|
100
|
+
file_server: OnceLock<StaticFileServer>,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[async_trait]
|
|
104
|
+
impl MiddlewareLayer for StaticAssets {
|
|
105
|
+
async fn initialize(&self) -> Result<()> {
|
|
106
|
+
if let Ok(metadata) = tokio::fs::metadata(&self.root_dir).await {
|
|
107
|
+
if metadata.is_dir() {
|
|
108
|
+
Ok(())
|
|
109
|
+
} else {
|
|
110
|
+
Err(ItsiError::InvalidInput(
|
|
111
|
+
"Root directory exists but is not a directory".to_string(),
|
|
112
|
+
))
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
Err(ItsiError::InvalidInput(
|
|
116
|
+
"Root directory exists but is not a directory".to_string(),
|
|
117
|
+
))
|
|
118
|
+
}?;
|
|
119
|
+
self.base_path_regex
|
|
120
|
+
.set(Regex::new(&self.base_path).map_err(ItsiError::new)?)
|
|
121
|
+
.map_err(ItsiError::new)?;
|
|
122
|
+
|
|
123
|
+
debug!(target: "middleware::static_assets", "Base path regexp: {}", self.base_path);
|
|
124
|
+
|
|
125
|
+
self.file_server
|
|
126
|
+
.set(StaticFileServer::new(StaticFileServerConfig {
|
|
127
|
+
root_dir: self.root_dir.clone(),
|
|
128
|
+
not_found_behavior: self.not_found_behavior.clone(),
|
|
129
|
+
auto_index: self.auto_index,
|
|
130
|
+
max_entries: self.max_files_in_memory,
|
|
131
|
+
try_html_extension: self.try_html_extension,
|
|
132
|
+
max_file_size: self.max_file_size_in_memory,
|
|
133
|
+
headers: self.headers.clone(),
|
|
134
|
+
recheck_interval: Duration::from_secs(self.file_check_interval),
|
|
135
|
+
serve_hidden_files: self.serve_hidden_files,
|
|
136
|
+
allowed_extensions: self.allowed_extensions.clone(),
|
|
137
|
+
miss_cache: Arc::new(Cache::new(self.max_files_in_memory as usize)),
|
|
138
|
+
})?)
|
|
139
|
+
.map_err(ItsiError::new)?;
|
|
140
|
+
Ok(())
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async fn before(
|
|
144
|
+
&self,
|
|
145
|
+
req: HttpRequest,
|
|
146
|
+
context: &mut HttpRequestContext,
|
|
147
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
|
148
|
+
// Only handle GET and HEAD requests
|
|
149
|
+
if req.method() != Method::GET && req.method() != Method::HEAD {
|
|
150
|
+
debug!(target: "middleware::static_assets", "Refusing to handle non-GET/HEAD request");
|
|
151
|
+
return Ok(Either::Left(req));
|
|
152
|
+
}
|
|
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.
|
|
156
|
+
context.set_supported_encoding_set(&req);
|
|
157
|
+
|
|
158
|
+
let abs_path = req.uri().path();
|
|
159
|
+
let rel_path = if !self.relative_path {
|
|
160
|
+
abs_path.trim_start_matches("/")
|
|
161
|
+
} else {
|
|
162
|
+
let base_path = self
|
|
163
|
+
.base_path_regex
|
|
164
|
+
.get()
|
|
165
|
+
.unwrap()
|
|
166
|
+
.captures(abs_path)
|
|
167
|
+
.and_then(|caps| caps.name("base_path"))
|
|
168
|
+
.map(|m| m.as_str())
|
|
169
|
+
.unwrap_or("/");
|
|
170
|
+
|
|
171
|
+
match abs_path.strip_prefix(base_path) {
|
|
172
|
+
Some(suffix) => suffix,
|
|
173
|
+
None => return Ok(Either::Left(req)),
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
debug!(target: "middleware::static_assets", "Asset path is {}", rel_path);
|
|
178
|
+
let is_head_request = req.method() == Method::HEAD;
|
|
179
|
+
|
|
180
|
+
// Extract range and if-modified-since headers
|
|
181
|
+
let serve_range = parse_range_header(req.headers());
|
|
182
|
+
let if_modified_since = req
|
|
183
|
+
.headers()
|
|
184
|
+
.get(IF_MODIFIED_SINCE)
|
|
185
|
+
.and_then(|ims| ims.to_str().ok())
|
|
186
|
+
.and_then(|ims_str| httpdate::parse_http_date(ims_str).ok());
|
|
187
|
+
|
|
188
|
+
// Let the file server handle everything
|
|
189
|
+
let file_server = self.file_server.get().unwrap();
|
|
190
|
+
let encodings: &[HeaderValue] = context
|
|
191
|
+
.supported_encoding_set()
|
|
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
|
+
|
|
209
|
+
let response = file_server
|
|
210
|
+
.serve(
|
|
211
|
+
&req,
|
|
212
|
+
rel_path,
|
|
213
|
+
abs_path,
|
|
214
|
+
serve_range,
|
|
215
|
+
if_modified_since,
|
|
216
|
+
is_head_request,
|
|
217
|
+
encodings_for_server,
|
|
218
|
+
)
|
|
219
|
+
.await;
|
|
220
|
+
|
|
221
|
+
if response.is_none() {
|
|
222
|
+
Ok(Either::Left(req))
|
|
223
|
+
} else {
|
|
224
|
+
Ok(Either::Right(response.unwrap()))
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
fn parse_range_header(headers: &HeaderMap) -> ServeRange {
|
|
230
|
+
let Some(range_header) = headers.get(RANGE) else {
|
|
231
|
+
return ServeRange::Full;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
let range_header = range_header.to_str().unwrap_or("");
|
|
235
|
+
let bytes_prefix = "bytes=";
|
|
236
|
+
if !range_header.starts_with(bytes_prefix) {
|
|
237
|
+
return ServeRange::Full;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Only consider the first range specifier, ignore multi-range requests.
|
|
241
|
+
let range_str = range_header[bytes_prefix.len()..]
|
|
242
|
+
.split(',')
|
|
243
|
+
.next()
|
|
244
|
+
.unwrap_or("");
|
|
245
|
+
|
|
246
|
+
let Some((start_str, end_str)) = range_str.split_once('-') else {
|
|
247
|
+
return ServeRange::Full;
|
|
248
|
+
};
|
|
249
|
+
|
|
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>() {
|
|
253
|
+
start
|
|
254
|
+
} else {
|
|
255
|
+
return ServeRange::Full;
|
|
256
|
+
};
|
|
257
|
+
|
|
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
|
|
262
|
+
} else {
|
|
263
|
+
return ServeRange::Full;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
ServeRange::Range(start, end)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
impl FromValue for StaticAssets {}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
use std::sync::OnceLock;
|
|
2
|
+
|
|
3
|
+
use super::{FromValue, MiddlewareLayer};
|
|
4
|
+
use crate::server::http_message_types::{HttpBody, HttpRequest, HttpResponse};
|
|
5
|
+
use crate::services::itsi_http_service::HttpRequestContext;
|
|
6
|
+
use async_trait::async_trait;
|
|
7
|
+
use bytes::Bytes;
|
|
8
|
+
use derive_more::Debug;
|
|
9
|
+
use either::Either;
|
|
10
|
+
use http::{HeaderMap, HeaderName, HeaderValue, Response, StatusCode};
|
|
11
|
+
use itsi_error::ItsiError;
|
|
12
|
+
use magnus::error::Result;
|
|
13
|
+
use serde::Deserialize;
|
|
14
|
+
|
|
15
|
+
#[derive(Debug, Deserialize)]
|
|
16
|
+
pub struct StaticResponse {
|
|
17
|
+
code: u16,
|
|
18
|
+
headers: Vec<(String, String)>,
|
|
19
|
+
body: Vec<u8>,
|
|
20
|
+
#[serde(skip)]
|
|
21
|
+
header_map: OnceLock<HeaderMap>,
|
|
22
|
+
#[serde(skip)]
|
|
23
|
+
body_bytes: OnceLock<Bytes>,
|
|
24
|
+
#[serde(skip)]
|
|
25
|
+
status_code: OnceLock<StatusCode>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[async_trait]
|
|
29
|
+
impl MiddlewareLayer for StaticResponse {
|
|
30
|
+
async fn initialize(&self) -> Result<()> {
|
|
31
|
+
let mut header_map = HeaderMap::new();
|
|
32
|
+
for (key, value) in self.headers.iter() {
|
|
33
|
+
if let (Ok(hn), Ok(hv)) = (key.parse::<HeaderName>(), value.parse::<HeaderValue>()) {
|
|
34
|
+
header_map.insert(hn, hv);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
self.header_map
|
|
38
|
+
.set(header_map)
|
|
39
|
+
.map_err(|_| ItsiError::new("Failed to set headers"))?;
|
|
40
|
+
self.body_bytes
|
|
41
|
+
.set(Bytes::from(self.body.clone()))
|
|
42
|
+
.map_err(|_| ItsiError::new("Failed to set body bytes"))?;
|
|
43
|
+
self.status_code
|
|
44
|
+
.set(StatusCode::from_u16(self.code).unwrap_or(StatusCode::OK))
|
|
45
|
+
.map_err(|_| ItsiError::new("Failed to set status code"))?;
|
|
46
|
+
Ok(())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async fn before(
|
|
50
|
+
&self,
|
|
51
|
+
_req: HttpRequest,
|
|
52
|
+
_context: &mut HttpRequestContext,
|
|
53
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
|
54
|
+
let mut resp = Response::new(HttpBody::full(self.body_bytes.get().unwrap().clone()));
|
|
55
|
+
*resp.status_mut() = *self.status_code.get().unwrap();
|
|
56
|
+
*resp.headers_mut() = self.header_map.get().unwrap().clone();
|
|
57
|
+
|
|
58
|
+
Ok(Either::Right(resp))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl FromValue for StaticResponse {}
|