itsi-scheduler 0.1.11 → 0.1.12
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/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +75 -14
- data/README.md +5 -0
- data/_index.md +7 -0
- data/ext/itsi_error/src/lib.rs +9 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_rb_helpers/Cargo.toml +1 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +18 -0
- data/ext/itsi_rb_helpers/src/lib.rs +34 -7
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_server/Cargo.toml +69 -30
- data/ext/itsi_server/src/lib.rs +79 -147
- data/ext/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
- data/ext/itsi_server/src/{body_proxy/itsi_body_proxy.rs → ruby_types/itsi_body_proxy/mod.rs} +22 -3
- data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
- data/ext/itsi_server/src/{request/itsi_request.rs → ruby_types/itsi_http_request.rs} +101 -117
- data/ext/itsi_server/src/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +72 -41
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
- data/ext/itsi_server/src/server/bind.rs +13 -5
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/cache_store.rs +74 -0
- data/ext/itsi_server/src/server/itsi_service.rs +172 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +3 -0
- data/ext/itsi_server/src/server/listener.rs +102 -2
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
- data/ext/itsi_server/src/server/mod.rs +8 -1
- data/ext/itsi_server/src/server/process_worker.rs +38 -12
- data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +119 -42
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +9 -6
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +256 -111
- data/ext/itsi_server/src/server/signal.rs +19 -0
- data/ext/itsi_server/src/server/static_file_server.rs +984 -0
- data/ext/itsi_server/src/server/thread_worker.rs +139 -94
- data/ext/itsi_server/src/server/types.rs +43 -0
- data/ext/itsi_tracing/Cargo.toml +1 -0
- data/ext/itsi_tracing/src/lib.rs +216 -45
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/scheduler/version.rb +1 -1
- data/lib/itsi/scheduler.rb +2 -2
- metadata +77 -12
- data/ext/itsi_server/extconf.rb +0 -6
- data/ext/itsi_server/src/body_proxy/mod.rs +0 -2
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/response/mod.rs +0 -1
- data/ext/itsi_server/src/server/itsi_server.rs +0 -288
@@ -0,0 +1,82 @@
|
|
1
|
+
use async_trait::async_trait;
|
2
|
+
use either::Either;
|
3
|
+
use itsi_tracing::*;
|
4
|
+
use magnus::error::Result;
|
5
|
+
use serde::Deserialize;
|
6
|
+
|
7
|
+
use crate::server::itsi_service::RequestContext;
|
8
|
+
use crate::server::types::{HttpRequest, HttpResponse};
|
9
|
+
|
10
|
+
use super::string_rewrite::StringRewrite;
|
11
|
+
use super::{FromValue, MiddlewareLayer};
|
12
|
+
|
13
|
+
/// Logging middleware for HTTP requests and responses
|
14
|
+
///
|
15
|
+
/// Supports customizable log formats with placeholders
|
16
|
+
#[derive(Debug, Clone, Deserialize)]
|
17
|
+
pub struct LogRequests {
|
18
|
+
pub before: Option<LogConfig>,
|
19
|
+
pub after: Option<LogConfig>,
|
20
|
+
}
|
21
|
+
|
22
|
+
#[derive(Debug, Clone, Deserialize)]
|
23
|
+
pub struct LogConfig {
|
24
|
+
level: LogMiddlewareLevel,
|
25
|
+
format: StringRewrite,
|
26
|
+
}
|
27
|
+
|
28
|
+
#[derive(Debug, Clone, Deserialize)]
|
29
|
+
pub enum LogMiddlewareLevel {
|
30
|
+
#[serde(rename(deserialize = "INFO"))]
|
31
|
+
Info,
|
32
|
+
#[serde(rename(deserialize = "TRACE"))]
|
33
|
+
Trace,
|
34
|
+
#[serde(rename(deserialize = "DEBUG"))]
|
35
|
+
Debug,
|
36
|
+
#[serde(rename(deserialize = "WARN"))]
|
37
|
+
Warn,
|
38
|
+
#[serde(rename(deserialize = "ERROR"))]
|
39
|
+
Error,
|
40
|
+
}
|
41
|
+
|
42
|
+
impl LogMiddlewareLevel {
|
43
|
+
pub fn log(&self, message: String) {
|
44
|
+
match self {
|
45
|
+
LogMiddlewareLevel::Trace => trace!(message),
|
46
|
+
LogMiddlewareLevel::Debug => debug!(message),
|
47
|
+
LogMiddlewareLevel::Info => info!(message),
|
48
|
+
LogMiddlewareLevel::Warn => warn!(message),
|
49
|
+
LogMiddlewareLevel::Error => error!(message),
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
#[async_trait]
|
55
|
+
impl MiddlewareLayer for LogRequests {
|
56
|
+
async fn initialize(&self) -> Result<()> {
|
57
|
+
Ok(())
|
58
|
+
}
|
59
|
+
|
60
|
+
async fn before(
|
61
|
+
&self,
|
62
|
+
req: HttpRequest,
|
63
|
+
context: &mut RequestContext,
|
64
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
65
|
+
context.track_start_time();
|
66
|
+
if let Some(LogConfig { level, format }) = self.before.as_ref() {
|
67
|
+
level.log(format.rewrite_request(&req, context));
|
68
|
+
}
|
69
|
+
|
70
|
+
Ok(Either::Left(req))
|
71
|
+
}
|
72
|
+
|
73
|
+
async fn after(&self, resp: HttpResponse, context: &mut RequestContext) -> HttpResponse {
|
74
|
+
if let Some(LogConfig { level, format }) = self.after.as_ref() {
|
75
|
+
level.log(format.rewrite_response(&resp, context));
|
76
|
+
}
|
77
|
+
|
78
|
+
resp
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
impl FromValue for LogRequests {}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
mod allow_list;
|
2
|
+
mod auth_api_key;
|
3
|
+
mod auth_basic;
|
4
|
+
mod auth_jwt;
|
5
|
+
mod cache_control;
|
6
|
+
mod compression;
|
7
|
+
mod cors;
|
8
|
+
mod deny_list;
|
9
|
+
mod error_response;
|
10
|
+
mod etag;
|
11
|
+
mod header_interpretation;
|
12
|
+
mod intrusion_protection;
|
13
|
+
mod log_requests;
|
14
|
+
mod proxy;
|
15
|
+
mod rate_limit;
|
16
|
+
mod redirect;
|
17
|
+
mod request_headers;
|
18
|
+
mod response_headers;
|
19
|
+
mod ruby_app;
|
20
|
+
mod static_assets;
|
21
|
+
mod string_rewrite;
|
22
|
+
mod token_source;
|
23
|
+
|
24
|
+
pub use allow_list::AllowList;
|
25
|
+
use async_trait::async_trait;
|
26
|
+
pub use auth_api_key::AuthAPIKey;
|
27
|
+
pub use auth_basic::AuthBasic;
|
28
|
+
pub use auth_jwt::AuthJwt;
|
29
|
+
pub use cache_control::CacheControl;
|
30
|
+
pub use compression::Compression;
|
31
|
+
pub use compression::CompressionAlgorithm;
|
32
|
+
pub use cors::Cors;
|
33
|
+
pub use deny_list::DenyList;
|
34
|
+
use either::Either;
|
35
|
+
pub use error_response::ErrorResponse;
|
36
|
+
pub use etag::ETag;
|
37
|
+
pub use intrusion_protection::IntrusionProtection;
|
38
|
+
pub use log_requests::LogRequests;
|
39
|
+
use magnus::error::Result;
|
40
|
+
use magnus::Value;
|
41
|
+
pub use proxy::Proxy;
|
42
|
+
pub use rate_limit::RateLimit;
|
43
|
+
pub use redirect::Redirect;
|
44
|
+
pub use request_headers::RequestHeaders;
|
45
|
+
pub use response_headers::ResponseHeaders;
|
46
|
+
pub use ruby_app::RubyApp;
|
47
|
+
use serde::Deserialize;
|
48
|
+
use serde_magnus::deserialize;
|
49
|
+
pub use static_assets::StaticAssets;
|
50
|
+
|
51
|
+
use crate::server::itsi_service::RequestContext;
|
52
|
+
use crate::server::types::{HttpRequest, HttpResponse};
|
53
|
+
|
54
|
+
pub trait FromValue: Sized + Send + Sync + 'static {
|
55
|
+
fn from_value(value: Value) -> Result<Self>
|
56
|
+
where
|
57
|
+
Self: Deserialize<'static>,
|
58
|
+
{
|
59
|
+
deserialize(value)
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
#[async_trait]
|
64
|
+
pub trait MiddlewareLayer: Sized + Send + Sync + 'static {
|
65
|
+
/// Called just once, to initialize the middleware state.
|
66
|
+
async fn initialize(&self) -> Result<()> {
|
67
|
+
Ok(())
|
68
|
+
}
|
69
|
+
/// The "before" hook. By default, it passes through the request.
|
70
|
+
async fn before(
|
71
|
+
&self,
|
72
|
+
req: HttpRequest,
|
73
|
+
_context: &mut RequestContext,
|
74
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
75
|
+
Ok(Either::Left(req))
|
76
|
+
}
|
77
|
+
|
78
|
+
/// The "after" hook. By default, it passes through the response.
|
79
|
+
async fn after(&self, resp: HttpResponse, _context: &mut RequestContext) -> HttpResponse {
|
80
|
+
resp
|
81
|
+
}
|
82
|
+
}
|
@@ -0,0 +1,216 @@
|
|
1
|
+
use std::{
|
2
|
+
collections::HashMap,
|
3
|
+
convert::Infallible,
|
4
|
+
net::SocketAddr,
|
5
|
+
sync::{Arc, OnceLock},
|
6
|
+
time::Duration,
|
7
|
+
};
|
8
|
+
|
9
|
+
use crate::server::{
|
10
|
+
bind::{Bind, BindAddress},
|
11
|
+
itsi_service::RequestContext,
|
12
|
+
types::{HttpRequest, HttpResponse},
|
13
|
+
};
|
14
|
+
|
15
|
+
use super::{string_rewrite::StringRewrite, ErrorResponse, FromValue, MiddlewareLayer};
|
16
|
+
|
17
|
+
use async_trait::async_trait;
|
18
|
+
use either::Either;
|
19
|
+
use futures::TryStreamExt;
|
20
|
+
use http::Response;
|
21
|
+
use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
|
22
|
+
use hyper::body::Frame;
|
23
|
+
use magnus::error::Result;
|
24
|
+
use reqwest::{dns::Resolve, Body, Client, Url};
|
25
|
+
use serde::Deserialize;
|
26
|
+
use tracing::error;
|
27
|
+
|
28
|
+
#[derive(Debug, Clone, Deserialize)]
|
29
|
+
pub struct Proxy {
|
30
|
+
pub to: StringRewrite,
|
31
|
+
pub backends: Vec<String>,
|
32
|
+
pub headers: HashMap<String, Option<ProxiedHeader>>,
|
33
|
+
pub verify_ssl: bool,
|
34
|
+
pub timeout: u64,
|
35
|
+
pub tls_sni: bool,
|
36
|
+
#[serde(skip_deserializing)]
|
37
|
+
pub client: OnceLock<Client>,
|
38
|
+
pub error_response: ErrorResponse,
|
39
|
+
}
|
40
|
+
|
41
|
+
#[derive(Debug, Clone, Deserialize)]
|
42
|
+
pub enum ProxiedHeader {
|
43
|
+
#[serde(rename(deserialize = "value"))]
|
44
|
+
String(String),
|
45
|
+
#[serde(rename(deserialize = "rewrite"))]
|
46
|
+
StringRewrite(StringRewrite),
|
47
|
+
}
|
48
|
+
|
49
|
+
impl ProxiedHeader {
|
50
|
+
pub fn to_string(&self, req: &HttpRequest, context: &RequestContext) -> String {
|
51
|
+
match self {
|
52
|
+
ProxiedHeader::String(value) => value.clone(),
|
53
|
+
ProxiedHeader::StringRewrite(rewrite) => rewrite.rewrite_request(req, context),
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
#[derive(Debug, Clone)]
|
59
|
+
pub struct Resolver {
|
60
|
+
backends: Arc<Vec<SocketAddr>>,
|
61
|
+
}
|
62
|
+
|
63
|
+
/// An iterator that owns an Arc to the backend list and iterates over it.
|
64
|
+
pub struct ResolverIter {
|
65
|
+
backends: Arc<Vec<SocketAddr>>,
|
66
|
+
index: usize,
|
67
|
+
}
|
68
|
+
|
69
|
+
impl Iterator for ResolverIter {
|
70
|
+
type Item = SocketAddr;
|
71
|
+
|
72
|
+
fn next(&mut self) -> Option<Self::Item> {
|
73
|
+
if self.index < self.backends.len() {
|
74
|
+
let addr = self.backends[self.index];
|
75
|
+
self.index += 1;
|
76
|
+
Some(addr)
|
77
|
+
} else {
|
78
|
+
None
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
impl Resolve for Resolver {
|
84
|
+
fn resolve(&self, _name: reqwest::dns::Name) -> reqwest::dns::Resolving {
|
85
|
+
let backends = self.backends.clone();
|
86
|
+
let fut = async move {
|
87
|
+
let iter = ResolverIter { backends, index: 0 };
|
88
|
+
Ok(Box::new(iter) as Box<dyn Iterator<Item = SocketAddr> + Send>)
|
89
|
+
};
|
90
|
+
Box::pin(fut)
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
#[async_trait]
|
95
|
+
impl MiddlewareLayer for Proxy {
|
96
|
+
async fn initialize(&self) -> Result<()> {
|
97
|
+
let backends = self
|
98
|
+
.backends
|
99
|
+
.iter()
|
100
|
+
.filter_map(|be| {
|
101
|
+
let bind: Bind = be.parse().ok()?;
|
102
|
+
match (bind.address, bind.port) {
|
103
|
+
(BindAddress::Ip(ip_addr), port) => {
|
104
|
+
Some(SocketAddr::new(ip_addr, port.unwrap()))
|
105
|
+
}
|
106
|
+
(BindAddress::UnixSocket(_), _) => None,
|
107
|
+
}
|
108
|
+
})
|
109
|
+
.collect::<Vec<_>>();
|
110
|
+
|
111
|
+
self.client
|
112
|
+
.set(
|
113
|
+
Client::builder()
|
114
|
+
.timeout(Duration::from_secs(self.timeout))
|
115
|
+
.danger_accept_invalid_certs(!self.verify_ssl)
|
116
|
+
.danger_accept_invalid_hostnames(!self.verify_ssl)
|
117
|
+
.dns_resolver(Arc::new(Resolver {
|
118
|
+
backends: Arc::new(backends),
|
119
|
+
}))
|
120
|
+
.tls_sni(self.tls_sni)
|
121
|
+
.build()
|
122
|
+
.map_err(|e| {
|
123
|
+
magnus::Error::new(
|
124
|
+
magnus::exception::runtime_error(),
|
125
|
+
format!("Failed to build Reqwest client: {}", e),
|
126
|
+
)
|
127
|
+
})?,
|
128
|
+
)
|
129
|
+
.map_err(|_e| {
|
130
|
+
magnus::Error::new(
|
131
|
+
magnus::exception::exception(),
|
132
|
+
"Failed to save resolver backends",
|
133
|
+
)
|
134
|
+
})?;
|
135
|
+
Ok(())
|
136
|
+
}
|
137
|
+
|
138
|
+
async fn before(
|
139
|
+
&self,
|
140
|
+
req: HttpRequest,
|
141
|
+
context: &mut RequestContext,
|
142
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
143
|
+
let url = self.to.rewrite_request(&req, context);
|
144
|
+
let error_response = self.error_response.to_http_response(&req).await;
|
145
|
+
|
146
|
+
let destination = match Url::parse(&url) {
|
147
|
+
Ok(dest) => dest,
|
148
|
+
Err(_) => return Ok(Either::Right(error_response)),
|
149
|
+
};
|
150
|
+
|
151
|
+
let host_str = destination.host_str().unwrap_or_else(|| {
|
152
|
+
req.headers()
|
153
|
+
.get("Host")
|
154
|
+
.and_then(|h| h.to_str().ok())
|
155
|
+
.unwrap_or("")
|
156
|
+
});
|
157
|
+
|
158
|
+
let mut reqwest_builder = self
|
159
|
+
.client
|
160
|
+
.get()
|
161
|
+
.unwrap()
|
162
|
+
.request(req.method().clone(), url);
|
163
|
+
|
164
|
+
// Forward incoming headers unless they're in remove_headers or overridden.
|
165
|
+
for (name, value) in req.headers().iter() {
|
166
|
+
let name_str = name.as_str();
|
167
|
+
if self.headers.contains_key(name_str) {
|
168
|
+
continue;
|
169
|
+
}
|
170
|
+
reqwest_builder = reqwest_builder.header(name, value);
|
171
|
+
}
|
172
|
+
|
173
|
+
// Add the host header if it's not overridden and host_str is non-empty.
|
174
|
+
if !self.headers.contains_key("host") && !host_str.is_empty() {
|
175
|
+
reqwest_builder = reqwest_builder.header("Host", host_str);
|
176
|
+
}
|
177
|
+
|
178
|
+
// Add overriding headers.
|
179
|
+
for (name, header_value) in self.headers.iter() {
|
180
|
+
if let Some(header_value) = header_value {
|
181
|
+
reqwest_builder =
|
182
|
+
reqwest_builder.header(name, header_value.to_string(&req, context));
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
let reqwest_builder = reqwest_builder.body(Body::wrap_stream(req.into_data_stream()));
|
187
|
+
let reqwest_response = reqwest_builder.send().await;
|
188
|
+
|
189
|
+
let response = match reqwest_response {
|
190
|
+
Ok(response) => {
|
191
|
+
let status = response.status();
|
192
|
+
let mut builder = Response::builder().status(status);
|
193
|
+
for (hn, hv) in response.headers() {
|
194
|
+
builder = builder.header(hn, hv);
|
195
|
+
}
|
196
|
+
let response = builder.body(BoxBody::new(StreamBody::new(
|
197
|
+
response
|
198
|
+
.bytes_stream()
|
199
|
+
.map_ok(Frame::data)
|
200
|
+
.map_err(|_| -> Infallible { unreachable!("We handle IO errors above") }),
|
201
|
+
)));
|
202
|
+
if let Ok(response) = response {
|
203
|
+
response
|
204
|
+
} else {
|
205
|
+
error_response
|
206
|
+
}
|
207
|
+
}
|
208
|
+
Err(e) => {
|
209
|
+
error!("Error sending request: {}", e);
|
210
|
+
error_response
|
211
|
+
}
|
212
|
+
};
|
213
|
+
Ok(Either::Right(response))
|
214
|
+
}
|
215
|
+
}
|
216
|
+
impl FromValue for Proxy {}
|
@@ -0,0 +1,124 @@
|
|
1
|
+
use super::{token_source::TokenSource, ErrorResponse, FromValue, MiddlewareLayer};
|
2
|
+
use crate::server::{
|
3
|
+
itsi_service::RequestContext,
|
4
|
+
rate_limiter::{
|
5
|
+
create_rate_limit_key, get_rate_limiter, RateLimitError, RateLimiter, RateLimiterConfig,
|
6
|
+
},
|
7
|
+
types::{HttpRequest, HttpResponse, RequestExt},
|
8
|
+
};
|
9
|
+
use async_trait::async_trait;
|
10
|
+
use either::Either;
|
11
|
+
use magnus::error::Result;
|
12
|
+
use serde::Deserialize;
|
13
|
+
use std::sync::{Arc, OnceLock};
|
14
|
+
use std::time::Duration;
|
15
|
+
|
16
|
+
#[derive(Debug, Clone, Deserialize)]
|
17
|
+
pub struct RateLimit {
|
18
|
+
pub requests: u64,
|
19
|
+
pub seconds: u64,
|
20
|
+
pub key: RateLimitKey,
|
21
|
+
#[serde(skip_deserializing)]
|
22
|
+
pub rate_limiter: OnceLock<Arc<dyn RateLimiter>>,
|
23
|
+
pub store_config: RateLimiterConfig,
|
24
|
+
pub error_response: ErrorResponse,
|
25
|
+
}
|
26
|
+
|
27
|
+
#[derive(Debug, Clone, Deserialize)]
|
28
|
+
pub enum RateLimitKey {
|
29
|
+
#[serde(rename(deserialize = "address"))]
|
30
|
+
SocketAddress,
|
31
|
+
#[serde(rename(deserialize = "parameter"))]
|
32
|
+
Parameter(TokenSource),
|
33
|
+
}
|
34
|
+
|
35
|
+
#[async_trait]
|
36
|
+
impl MiddlewareLayer for RateLimit {
|
37
|
+
async fn initialize(&self) -> Result<()> {
|
38
|
+
// Instantiate our rate limiter based on the rate limit config here.
|
39
|
+
// This will automatically fall back to in-memory if Redis fails
|
40
|
+
if let Ok(limiter) = get_rate_limiter(&self.store_config).await {
|
41
|
+
let _ = self.rate_limiter.set(limiter);
|
42
|
+
}
|
43
|
+
Ok(())
|
44
|
+
}
|
45
|
+
|
46
|
+
async fn before(
|
47
|
+
&self,
|
48
|
+
req: HttpRequest,
|
49
|
+
context: &mut RequestContext,
|
50
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
51
|
+
// Get the key to rate limit on
|
52
|
+
let key_value = match &self.key {
|
53
|
+
RateLimitKey::SocketAddress => {
|
54
|
+
// Use the socket address from the context
|
55
|
+
&context.addr
|
56
|
+
}
|
57
|
+
RateLimitKey::Parameter(token_source) => {
|
58
|
+
match token_source {
|
59
|
+
TokenSource::Header { name, prefix } => {
|
60
|
+
if let Some(header) = req.header(name) {
|
61
|
+
if let Some(prefix) = prefix {
|
62
|
+
header.strip_prefix(prefix).unwrap_or("").trim_ascii()
|
63
|
+
} else {
|
64
|
+
header.trim_ascii()
|
65
|
+
}
|
66
|
+
} else {
|
67
|
+
// If no token is found, skip rate limiting
|
68
|
+
tracing::warn!("No token found in header for rate limiting");
|
69
|
+
return Ok(Either::Left(req));
|
70
|
+
}
|
71
|
+
}
|
72
|
+
TokenSource::Query(query_name) => {
|
73
|
+
if let Some(value) = req.query_param(query_name) {
|
74
|
+
value
|
75
|
+
} else {
|
76
|
+
// If no token is found, skip rate limiting
|
77
|
+
tracing::warn!("No token found in query for rate limiting");
|
78
|
+
return Ok(Either::Left(req));
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
};
|
84
|
+
|
85
|
+
// Create a rate limit key
|
86
|
+
let rate_limit_key = create_rate_limit_key(key_value, req.uri().path());
|
87
|
+
|
88
|
+
// Get the rate limiter
|
89
|
+
if let Some(limiter) = self.rate_limiter.get() {
|
90
|
+
// Check if rate limit is exceeded
|
91
|
+
let timeout = Duration::from_secs(self.seconds);
|
92
|
+
let limit = self.requests;
|
93
|
+
|
94
|
+
match limiter.check_limit(&rate_limit_key, limit, timeout).await {
|
95
|
+
Ok(_) => {
|
96
|
+
// Rate limit not exceeded, allow request
|
97
|
+
Ok(Either::Left(req))
|
98
|
+
}
|
99
|
+
Err(RateLimitError::RateLimitExceeded { limit, count }) => {
|
100
|
+
// Rate limit exceeded, return error response
|
101
|
+
tracing::info!(
|
102
|
+
"Rate limit exceeded for key '{}': {}/{} requests",
|
103
|
+
rate_limit_key,
|
104
|
+
count,
|
105
|
+
limit
|
106
|
+
);
|
107
|
+
Ok(Either::Right(
|
108
|
+
self.error_response.to_http_response(&req).await,
|
109
|
+
))
|
110
|
+
}
|
111
|
+
Err(e) => {
|
112
|
+
// Other error, log and allow request (fail open)
|
113
|
+
tracing::error!("Rate limiter error: {:?}", e);
|
114
|
+
Ok(Either::Left(req))
|
115
|
+
}
|
116
|
+
}
|
117
|
+
} else {
|
118
|
+
// If rate limiter is not initialized, allow request
|
119
|
+
tracing::warn!("Rate limiter not initialized");
|
120
|
+
Ok(Either::Left(req))
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
impl FromValue for RateLimit {}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
use crate::server::{
|
2
|
+
itsi_service::RequestContext,
|
3
|
+
types::{HttpRequest, HttpResponse},
|
4
|
+
};
|
5
|
+
|
6
|
+
use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
|
7
|
+
|
8
|
+
use async_trait::async_trait;
|
9
|
+
use either::Either;
|
10
|
+
use http::{Response, StatusCode};
|
11
|
+
use http_body_util::{combinators::BoxBody, Empty};
|
12
|
+
use magnus::error::Result;
|
13
|
+
use serde::Deserialize;
|
14
|
+
|
15
|
+
/// A simple API key filter.
|
16
|
+
/// The API key can be given inside the header or a query string
|
17
|
+
/// Keys are validated against a list of allowed key values (Changing these requires a restart)
|
18
|
+
///
|
19
|
+
#[derive(Debug, Clone, Deserialize)]
|
20
|
+
pub struct Redirect {
|
21
|
+
pub to: StringRewrite,
|
22
|
+
#[serde(default)]
|
23
|
+
#[serde(rename(deserialize = "type"))]
|
24
|
+
pub redirect_type: RedirectType,
|
25
|
+
}
|
26
|
+
|
27
|
+
#[derive(Debug, Clone, Deserialize, Default)]
|
28
|
+
pub enum RedirectType {
|
29
|
+
#[serde(rename(deserialize = "permanent"))]
|
30
|
+
#[default]
|
31
|
+
Permanent,
|
32
|
+
#[serde(rename(deserialize = "temporary"))]
|
33
|
+
Temporary,
|
34
|
+
#[serde(rename(deserialize = "found"))]
|
35
|
+
Found,
|
36
|
+
#[serde(rename(deserialize = "moved_permanently"))]
|
37
|
+
MovedPermanently,
|
38
|
+
}
|
39
|
+
|
40
|
+
#[async_trait]
|
41
|
+
impl MiddlewareLayer for Redirect {
|
42
|
+
async fn before(
|
43
|
+
&self,
|
44
|
+
req: HttpRequest,
|
45
|
+
context: &mut RequestContext,
|
46
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
47
|
+
Ok(Either::Right(self.redirect_response(&req, context)?))
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
impl Redirect {
|
52
|
+
pub fn redirect_response(
|
53
|
+
&self,
|
54
|
+
req: &HttpRequest,
|
55
|
+
context: &mut RequestContext,
|
56
|
+
) -> Result<HttpResponse> {
|
57
|
+
let mut response = Response::new(BoxBody::new(Empty::new()));
|
58
|
+
*response.status_mut() = match self.redirect_type {
|
59
|
+
RedirectType::Permanent => StatusCode::PERMANENT_REDIRECT,
|
60
|
+
RedirectType::Temporary => StatusCode::TEMPORARY_REDIRECT,
|
61
|
+
RedirectType::MovedPermanently => StatusCode::MOVED_PERMANENTLY,
|
62
|
+
RedirectType::Found => StatusCode::FOUND,
|
63
|
+
};
|
64
|
+
response.headers_mut().append(
|
65
|
+
"Location",
|
66
|
+
self.to.rewrite_request(req, context).parse().map_err(|e| {
|
67
|
+
magnus::Error::new(
|
68
|
+
magnus::exception::exception(),
|
69
|
+
format!("Invalid Rewrite String: {:?}: {}", self.to, e),
|
70
|
+
)
|
71
|
+
})?,
|
72
|
+
);
|
73
|
+
Ok(response)
|
74
|
+
}
|
75
|
+
}
|
76
|
+
impl FromValue for Redirect {}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
use std::collections::HashMap;
|
2
|
+
|
3
|
+
use super::{FromValue, MiddlewareLayer};
|
4
|
+
use crate::server::{
|
5
|
+
itsi_service::RequestContext,
|
6
|
+
types::{HttpRequest, HttpResponse},
|
7
|
+
};
|
8
|
+
use async_trait::async_trait;
|
9
|
+
use either::Either;
|
10
|
+
use http::HeaderName;
|
11
|
+
use magnus::error::Result;
|
12
|
+
use serde::Deserialize;
|
13
|
+
|
14
|
+
#[derive(Debug, Clone, Deserialize)]
|
15
|
+
pub struct RequestHeaders {
|
16
|
+
pub additions: HashMap<String, Vec<String>>,
|
17
|
+
pub removals: Vec<String>,
|
18
|
+
}
|
19
|
+
|
20
|
+
#[async_trait]
|
21
|
+
impl MiddlewareLayer for RequestHeaders {
|
22
|
+
async fn before(
|
23
|
+
&self,
|
24
|
+
mut req: HttpRequest,
|
25
|
+
_: &mut RequestContext,
|
26
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
27
|
+
let headers = req.headers_mut();
|
28
|
+
for removal in &self.removals {
|
29
|
+
headers.remove(removal);
|
30
|
+
}
|
31
|
+
for (header_name, header_values) in &self.additions {
|
32
|
+
for header_value in header_values {
|
33
|
+
if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
|
34
|
+
if let Ok(parsed_header_value) = header_value.parse() {
|
35
|
+
headers.append(parsed_header_name, parsed_header_value);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
Ok(Either::Left(req))
|
41
|
+
}
|
42
|
+
}
|
43
|
+
impl FromValue for RequestHeaders {}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
use std::collections::HashMap;
|
2
|
+
|
3
|
+
use super::{FromValue, MiddlewareLayer};
|
4
|
+
use crate::server::{itsi_service::RequestContext, types::HttpResponse};
|
5
|
+
use async_trait::async_trait;
|
6
|
+
use http::HeaderName;
|
7
|
+
use serde::Deserialize;
|
8
|
+
|
9
|
+
#[derive(Debug, Clone, Deserialize)]
|
10
|
+
pub struct ResponseHeaders {
|
11
|
+
pub additions: HashMap<String, Vec<String>>,
|
12
|
+
pub removals: Vec<String>,
|
13
|
+
}
|
14
|
+
|
15
|
+
#[async_trait]
|
16
|
+
impl MiddlewareLayer for ResponseHeaders {
|
17
|
+
async fn after(&self, mut resp: HttpResponse, _: &mut RequestContext) -> HttpResponse {
|
18
|
+
let headers = resp.headers_mut();
|
19
|
+
for removal in &self.removals {
|
20
|
+
headers.remove(removal);
|
21
|
+
}
|
22
|
+
for (header_name, header_values) in &self.additions {
|
23
|
+
for header_value in header_values {
|
24
|
+
if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
|
25
|
+
if let Ok(parsed_header_value) = header_value.parse() {
|
26
|
+
headers.append(parsed_header_name, parsed_header_value);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
resp
|
32
|
+
}
|
33
|
+
}
|
34
|
+
impl FromValue for ResponseHeaders {}
|