itsi 0.1.14 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +124 -109
- data/Cargo.toml +6 -0
- data/crates/itsi_error/Cargo.toml +1 -0
- data/crates/itsi_error/src/lib.rs +100 -10
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
- data/crates/itsi_server/Cargo.toml +8 -10
- data/crates/itsi_server/src/default_responses/html/401.html +68 -0
- data/crates/itsi_server/src/default_responses/html/403.html +68 -0
- data/crates/itsi_server/src/default_responses/html/404.html +68 -0
- data/crates/itsi_server/src/default_responses/html/413.html +71 -0
- data/crates/itsi_server/src/default_responses/html/429.html +68 -0
- data/crates/itsi_server/src/default_responses/html/500.html +71 -0
- data/crates/itsi_server/src/default_responses/html/502.html +71 -0
- data/crates/itsi_server/src/default_responses/html/503.html +68 -0
- data/crates/itsi_server/src/default_responses/html/504.html +69 -0
- data/crates/itsi_server/src/default_responses/html/index.html +238 -0
- data/crates/itsi_server/src/default_responses/json/401.json +6 -0
- data/crates/itsi_server/src/default_responses/json/403.json +6 -0
- data/crates/itsi_server/src/default_responses/json/404.json +6 -0
- data/crates/itsi_server/src/default_responses/json/413.json +6 -0
- data/crates/itsi_server/src/default_responses/json/429.json +6 -0
- data/crates/itsi_server/src/default_responses/json/500.json +6 -0
- data/crates/itsi_server/src/default_responses/json/502.json +6 -0
- data/crates/itsi_server/src/default_responses/json/503.json +6 -0
- data/crates/itsi_server/src/default_responses/json/504.json +6 -0
- data/crates/itsi_server/src/default_responses/mod.rs +11 -0
- data/crates/itsi_server/src/lib.rs +58 -26
- data/crates/itsi_server/src/prelude.rs +2 -0
- data/crates/itsi_server/src/ruby_types/README.md +21 -0
- data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
- data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
- data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
- data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
- data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
- data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
- data/crates/itsi_server/src/server/binds/mod.rs +4 -0
- data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
- data/crates/itsi_server/src/server/http_message_types.rs +97 -0
- data/crates/itsi_server/src/server/io_stream.rs +2 -1
- data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
- data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +50 -29
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
- data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
- data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
- data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
- data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
- data/crates/itsi_server/src/server/mod.rs +3 -9
- data/crates/itsi_server/src/server/process_worker.rs +21 -3
- data/crates/itsi_server/src/server/request_job.rs +2 -2
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
- data/crates/itsi_server/src/server/signal.rs +24 -41
- data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/crates/itsi_server/src/server/thread_worker.rs +59 -28
- data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
- data/crates/itsi_server/src/services/mime_types.rs +1416 -0
- data/crates/itsi_server/src/services/mod.rs +6 -0
- data/crates/itsi_server/src/services/password_hasher.rs +83 -0
- data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
- data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
- data/crates/itsi_tracing/src/lib.rs +145 -55
- data/{Itsi.rb → foo/Itsi.rb} +6 -9
- data/gems/scheduler/Cargo.lock +7 -0
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/test/helpers/test_helper.rb +0 -1
- data/gems/scheduler/test/test_address_resolve.rb +0 -1
- data/gems/scheduler/test/test_network_io.rb +1 -1
- data/gems/scheduler/test/test_process_wait.rb +0 -1
- data/gems/server/Cargo.lock +124 -109
- data/gems/server/exe/itsi +65 -19
- data/gems/server/itsi-server.gemspec +4 -3
- data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
- data/gems/server/lib/itsi/http_request.rb +116 -17
- data/gems/server/lib/itsi/http_response.rb +2 -0
- data/gems/server/lib/itsi/passfile.rb +109 -0
- data/gems/server/lib/itsi/server/config/dsl.rb +160 -101
- data/gems/server/lib/itsi/server/config.rb +58 -23
- data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
- data/gems/server/lib/itsi/server/default_app/index.html +113 -89
- data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
- data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
- data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
- data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
- data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
- data/gems/server/lib/itsi/server/route_tester.rb +107 -0
- data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
- data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
- data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +82 -12
- data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
- data/gems/server/lib/shell_completions/completions.rb +26 -0
- data/gems/server/test/helpers/test_helper.rb +2 -1
- data/lib/itsi/version.rb +1 -1
- data/sandbox/README.md +5 -0
- data/sandbox/itsi_file/Gemfile +4 -2
- data/sandbox/itsi_file/Gemfile.lock +48 -6
- data/sandbox/itsi_file/Itsi.rb +326 -129
- data/sandbox/itsi_file/call.json +1 -0
- data/sandbox/itsi_file/echo_client/Gemfile +10 -0
- data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
- data/sandbox/itsi_file/echo_client/README.md +95 -0
- data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
- data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
- data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
- data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
- data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
- data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
- data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
- data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
- data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
- data/sandbox/itsi_sandbox_async/config.ru +0 -1
- data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
- data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
- data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
- data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
- data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
- data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
- data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
- data/sandbox/itsi_sinatra/app.rb +0 -1
- data/sandbox/static_files/.env +1 -0
- data/sandbox/static_files/404.html +25 -0
- data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
- data/sandbox/static_files/about.html +68 -0
- data/sandbox/static_files/tiny.html +1 -0
- data/sandbox/static_files/writebook.zip +0 -0
- data/tasks.txt +28 -33
- metadata +87 -26
- data/crates/itsi_error/src/from.rs +0 -68
- data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
- data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
- data/crates/itsi_server/src/server/itsi_service.rs +0 -172
- data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
- data/crates/itsi_server/src/server/types.rs +0 -43
- data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
- data/sandbox/itsi_file/public/assets/index.html +0 -1
- /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
- /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
- /data/crates/itsi_server/src/{server → services}/cache_store.rs +0 -0
@@ -1,43 +1,68 @@
|
|
1
1
|
use std::{
|
2
2
|
collections::HashMap,
|
3
3
|
convert::Infallible,
|
4
|
+
error::Error,
|
4
5
|
net::SocketAddr,
|
5
|
-
sync::{
|
6
|
+
sync::{
|
7
|
+
atomic::{AtomicUsize, Ordering},
|
8
|
+
Arc, LazyLock, OnceLock,
|
9
|
+
},
|
6
10
|
time::Duration,
|
7
11
|
};
|
8
12
|
|
9
|
-
use crate::server::{
|
10
|
-
bind::{Bind, BindAddress},
|
11
|
-
itsi_service::RequestContext,
|
12
|
-
types::{HttpRequest, HttpResponse},
|
13
|
-
};
|
14
|
-
|
15
13
|
use super::{string_rewrite::StringRewrite, ErrorResponse, FromValue, MiddlewareLayer};
|
16
|
-
|
14
|
+
use crate::{
|
15
|
+
server::{
|
16
|
+
binds::bind::{Bind, BindAddress},
|
17
|
+
http_message_types::{HttpRequest, HttpResponse, RequestExt, ResponseFormat},
|
18
|
+
size_limited_incoming::MaxBodySizeReached,
|
19
|
+
},
|
20
|
+
services::itsi_http_service::HttpRequestContext,
|
21
|
+
};
|
17
22
|
use async_trait::async_trait;
|
23
|
+
use bytes::{Bytes, BytesMut};
|
18
24
|
use either::Either;
|
19
25
|
use futures::TryStreamExt;
|
20
|
-
use http::Response;
|
21
|
-
use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
|
26
|
+
use http::{HeaderMap, Method, Response, StatusCode};
|
27
|
+
use http_body_util::{combinators::BoxBody, BodyExt, Empty, StreamBody};
|
22
28
|
use hyper::body::Frame;
|
23
29
|
use magnus::error::Result;
|
24
|
-
use
|
30
|
+
use rand::Rng;
|
31
|
+
use reqwest::{
|
32
|
+
dns::{Name, Resolve},
|
33
|
+
Body, Client, Url,
|
34
|
+
};
|
25
35
|
use serde::Deserialize;
|
26
|
-
use tracing::error;
|
27
36
|
|
28
37
|
#[derive(Debug, Clone, Deserialize)]
|
29
38
|
pub struct Proxy {
|
30
39
|
pub to: StringRewrite,
|
31
40
|
pub backends: Vec<String>,
|
41
|
+
pub backend_priority: BackendPriority,
|
32
42
|
pub headers: HashMap<String, Option<ProxiedHeader>>,
|
33
43
|
pub verify_ssl: bool,
|
34
44
|
pub timeout: u64,
|
35
45
|
pub tls_sni: bool,
|
36
46
|
#[serde(skip_deserializing)]
|
37
47
|
pub client: OnceLock<Client>,
|
48
|
+
#[serde(default = "bad_gateway_error_response")]
|
38
49
|
pub error_response: ErrorResponse,
|
39
50
|
}
|
40
51
|
|
52
|
+
fn bad_gateway_error_response() -> ErrorResponse {
|
53
|
+
ErrorResponse::bad_gateway()
|
54
|
+
}
|
55
|
+
|
56
|
+
#[derive(Debug, Clone, Deserialize)]
|
57
|
+
pub enum BackendPriority {
|
58
|
+
#[serde(rename(deserialize = "round_robin"))]
|
59
|
+
RoundRobin,
|
60
|
+
#[serde(rename(deserialize = "ordered"))]
|
61
|
+
Ordered,
|
62
|
+
#[serde(rename(deserialize = "random"))]
|
63
|
+
Random,
|
64
|
+
}
|
65
|
+
|
41
66
|
#[derive(Debug, Clone, Deserialize)]
|
42
67
|
pub enum ProxiedHeader {
|
43
68
|
#[serde(rename(deserialize = "value"))]
|
@@ -47,7 +72,7 @@ pub enum ProxiedHeader {
|
|
47
72
|
}
|
48
73
|
|
49
74
|
impl ProxiedHeader {
|
50
|
-
pub fn to_string(&self, req: &HttpRequest, context: &
|
75
|
+
pub fn to_string(&self, req: &HttpRequest, context: &HttpRequestContext) -> String {
|
51
76
|
match self {
|
52
77
|
ProxiedHeader::String(value) => value.clone(),
|
53
78
|
ProxiedHeader::StringRewrite(rewrite) => rewrite.rewrite_request(req, context),
|
@@ -58,22 +83,24 @@ impl ProxiedHeader {
|
|
58
83
|
#[derive(Debug, Clone)]
|
59
84
|
pub struct Resolver {
|
60
85
|
backends: Arc<Vec<SocketAddr>>,
|
86
|
+
counter: Arc<AtomicUsize>,
|
87
|
+
backend_priority: BackendPriority,
|
61
88
|
}
|
62
89
|
|
63
|
-
|
64
|
-
pub struct ResolverIter {
|
90
|
+
pub struct StatefulResolverIter {
|
65
91
|
backends: Arc<Vec<SocketAddr>>,
|
66
|
-
|
92
|
+
start_index: usize,
|
93
|
+
current: usize,
|
67
94
|
}
|
68
95
|
|
69
|
-
impl Iterator for
|
96
|
+
impl Iterator for StatefulResolverIter {
|
70
97
|
type Item = SocketAddr;
|
71
98
|
|
72
99
|
fn next(&mut self) -> Option<Self::Item> {
|
73
|
-
if self.
|
74
|
-
let
|
75
|
-
self.
|
76
|
-
Some(
|
100
|
+
if self.current < self.backends.len() {
|
101
|
+
let index = (self.start_index + self.current) % self.backends.len();
|
102
|
+
self.current += 1;
|
103
|
+
Some(self.backends[index])
|
77
104
|
} else {
|
78
105
|
None
|
79
106
|
}
|
@@ -81,16 +108,160 @@ impl Iterator for ResolverIter {
|
|
81
108
|
}
|
82
109
|
|
83
110
|
impl Resolve for Resolver {
|
84
|
-
fn resolve(&self, _name:
|
111
|
+
fn resolve(&self, _name: Name) -> reqwest::dns::Resolving {
|
85
112
|
let backends = self.backends.clone();
|
113
|
+
let len = backends.len();
|
114
|
+
|
115
|
+
let start_index = match self.backend_priority {
|
116
|
+
BackendPriority::Ordered => 0,
|
117
|
+
BackendPriority::Random => rand::rng().random_range(0..len),
|
118
|
+
BackendPriority::RoundRobin => self.counter.fetch_add(1, Ordering::Relaxed) % len,
|
119
|
+
};
|
120
|
+
|
86
121
|
let fut = async move {
|
87
|
-
let iter =
|
122
|
+
let iter = StatefulResolverIter {
|
123
|
+
backends,
|
124
|
+
start_index,
|
125
|
+
current: 0,
|
126
|
+
};
|
88
127
|
Ok(Box::new(iter) as Box<dyn Iterator<Item = SocketAddr> + Send>)
|
89
128
|
};
|
129
|
+
|
90
130
|
Box::pin(fut)
|
91
131
|
}
|
92
132
|
}
|
93
133
|
|
134
|
+
static BAD_GATEWAY_RESPONSE: LazyLock<ErrorResponse> = LazyLock::new(ErrorResponse::bad_gateway);
|
135
|
+
static GATEWAY_TIMEOUT_RESPONSE: LazyLock<ErrorResponse> =
|
136
|
+
LazyLock::new(ErrorResponse::gateway_timeout);
|
137
|
+
static SERVICE_UNAVAILABLE_RESPONSE: LazyLock<ErrorResponse> =
|
138
|
+
LazyLock::new(ErrorResponse::service_unavailable);
|
139
|
+
static INTERNAL_SERVER_ERROR_RESPONSE: LazyLock<ErrorResponse> =
|
140
|
+
LazyLock::new(ErrorResponse::internal_server_error);
|
141
|
+
|
142
|
+
fn is_idempotent(method: &Method) -> bool {
|
143
|
+
matches!(
|
144
|
+
*method,
|
145
|
+
Method::GET | Method::HEAD | Method::PUT | Method::DELETE | Method::OPTIONS
|
146
|
+
)
|
147
|
+
}
|
148
|
+
|
149
|
+
/// A helper that stores the immutable parts of the incoming request.
|
150
|
+
struct RequestInfo {
|
151
|
+
method: Method,
|
152
|
+
headers: HeaderMap,
|
153
|
+
}
|
154
|
+
|
155
|
+
impl Proxy {
|
156
|
+
/// Build a header map of overriding headers based on the configured values.
|
157
|
+
/// This uses the full HttpRequest and context to compute each header value.
|
158
|
+
fn build_overriding_headers(
|
159
|
+
&self,
|
160
|
+
req: &HttpRequest,
|
161
|
+
context: &mut HttpRequestContext,
|
162
|
+
) -> http::HeaderMap {
|
163
|
+
let mut headers = http::HeaderMap::new();
|
164
|
+
for (name, header_opt) in self.headers.iter() {
|
165
|
+
if let Some(header_value) = header_opt {
|
166
|
+
// Compute the header value using the full HttpRequest.
|
167
|
+
let value_str = header_value.to_string(req, context);
|
168
|
+
if let Ok(header_val) = http::HeaderValue::from_str(&value_str) {
|
169
|
+
if let Ok(header_name) = name.parse::<http::header::HeaderName>() {
|
170
|
+
headers.insert(header_name, header_val);
|
171
|
+
}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
headers
|
176
|
+
}
|
177
|
+
|
178
|
+
/// Build a reqwest::RequestBuilder by merging the original request headers
|
179
|
+
/// (unless overridden) with the precomputed overriding headers.
|
180
|
+
fn build_reqwest_request_info(
|
181
|
+
&self,
|
182
|
+
req_info: &RequestInfo,
|
183
|
+
url: &str,
|
184
|
+
host_str: &str,
|
185
|
+
body: Body,
|
186
|
+
overriding_headers: &http::HeaderMap,
|
187
|
+
) -> reqwest::RequestBuilder {
|
188
|
+
let mut builder = self
|
189
|
+
.client
|
190
|
+
.get()
|
191
|
+
.unwrap()
|
192
|
+
.request(req_info.method.clone(), url);
|
193
|
+
|
194
|
+
// Forward headers from the original request unless they are overridden.
|
195
|
+
for (name, value) in req_info.headers.iter() {
|
196
|
+
if overriding_headers.contains_key(name) {
|
197
|
+
continue;
|
198
|
+
}
|
199
|
+
builder = builder.header(name, value);
|
200
|
+
}
|
201
|
+
|
202
|
+
// Add a Host header if not overridden.
|
203
|
+
if !overriding_headers.contains_key("host") && !host_str.is_empty() {
|
204
|
+
builder = builder.header("Host", host_str);
|
205
|
+
}
|
206
|
+
|
207
|
+
for (name, value) in overriding_headers.iter() {
|
208
|
+
builder = builder.header(name, value);
|
209
|
+
}
|
210
|
+
|
211
|
+
builder.body(body)
|
212
|
+
}
|
213
|
+
|
214
|
+
/// Sends an idempotent request using a replayable (buffered) body.
|
215
|
+
async fn send_request_idempotent(
|
216
|
+
&self,
|
217
|
+
req_info: &RequestInfo,
|
218
|
+
url: &str,
|
219
|
+
host_str: &str,
|
220
|
+
max_attempts: usize,
|
221
|
+
replayable_bytes: Bytes,
|
222
|
+
overriding_headers: &http::HeaderMap,
|
223
|
+
) -> std::result::Result<reqwest::Response, reqwest::Error> {
|
224
|
+
let mut last_err = None;
|
225
|
+
for attempt in 0..max_attempts {
|
226
|
+
let body = Body::from(replayable_bytes.clone());
|
227
|
+
let builder =
|
228
|
+
self.build_reqwest_request_info(req_info, url, host_str, body, overriding_headers);
|
229
|
+
match builder.send().await {
|
230
|
+
Ok(response) => return Ok(response),
|
231
|
+
Err(e) => {
|
232
|
+
// Retry for connectivity-related errors.
|
233
|
+
if e.is_connect() {
|
234
|
+
last_err = Some(e);
|
235
|
+
if attempt + 1 < max_attempts {
|
236
|
+
continue;
|
237
|
+
} else {
|
238
|
+
break;
|
239
|
+
}
|
240
|
+
} else {
|
241
|
+
return Err(e);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
245
|
+
}
|
246
|
+
Err(last_err.expect("At least one attempt should have set last_err"))
|
247
|
+
}
|
248
|
+
|
249
|
+
/// Sends a non-idempotent request once using its streaming body.
|
250
|
+
async fn send_request_non_idempotent(
|
251
|
+
&self,
|
252
|
+
req: HttpRequest,
|
253
|
+
req_info: &RequestInfo,
|
254
|
+
url: &str,
|
255
|
+
host_str: &str,
|
256
|
+
overriding_headers: &http::HeaderMap,
|
257
|
+
) -> std::result::Result<reqwest::Response, reqwest::Error> {
|
258
|
+
let body = Body::wrap_stream(req.into_data_stream());
|
259
|
+
let builder =
|
260
|
+
self.build_reqwest_request_info(req_info, url, host_str, body, overriding_headers);
|
261
|
+
builder.send().await
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
94
265
|
#[async_trait]
|
95
266
|
impl MiddlewareLayer for Proxy {
|
96
267
|
async fn initialize(&self) -> Result<()> {
|
@@ -116,6 +287,8 @@ impl MiddlewareLayer for Proxy {
|
|
116
287
|
.danger_accept_invalid_hostnames(!self.verify_ssl)
|
117
288
|
.dns_resolver(Arc::new(Resolver {
|
118
289
|
backends: Arc::new(backends),
|
290
|
+
counter: Arc::new(AtomicUsize::new(0)),
|
291
|
+
backend_priority: self.backend_priority.clone(),
|
119
292
|
}))
|
120
293
|
.tls_sni(self.tls_sni)
|
121
294
|
.build()
|
@@ -128,7 +301,7 @@ impl MiddlewareLayer for Proxy {
|
|
128
301
|
)
|
129
302
|
.map_err(|_e| {
|
130
303
|
magnus::Error::new(
|
131
|
-
magnus::exception::
|
304
|
+
magnus::exception::standard_error(),
|
132
305
|
"Failed to save resolver backends",
|
133
306
|
)
|
134
307
|
})?;
|
@@ -138,55 +311,67 @@ impl MiddlewareLayer for Proxy {
|
|
138
311
|
async fn before(
|
139
312
|
&self,
|
140
313
|
req: HttpRequest,
|
141
|
-
context: &mut
|
314
|
+
context: &mut HttpRequestContext,
|
142
315
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
143
316
|
let url = self.to.rewrite_request(&req, context);
|
144
|
-
let
|
317
|
+
let accept: ResponseFormat = req.accept().into();
|
318
|
+
let error_response = self.error_response.to_http_response(accept.clone()).await;
|
145
319
|
|
146
320
|
let destination = match Url::parse(&url) {
|
147
321
|
Ok(dest) => dest,
|
148
322
|
Err(_) => return Ok(Either::Right(error_response)),
|
149
323
|
};
|
150
324
|
|
325
|
+
// Clone the headers before consuming the request.
|
326
|
+
let req_headers = req.headers().clone();
|
151
327
|
let host_str = destination.host_str().unwrap_or_else(|| {
|
152
|
-
|
328
|
+
req_headers
|
153
329
|
.get("Host")
|
154
330
|
.and_then(|h| h.to_str().ok())
|
155
331
|
.unwrap_or("")
|
156
332
|
});
|
157
333
|
|
158
|
-
let
|
159
|
-
.
|
160
|
-
.
|
161
|
-
|
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
|
-
}
|
334
|
+
let req_info = RequestInfo {
|
335
|
+
method: req.method().clone(),
|
336
|
+
headers: req_headers.clone(),
|
337
|
+
};
|
172
338
|
|
173
|
-
//
|
174
|
-
|
175
|
-
reqwest_builder = reqwest_builder.header("Host", host_str);
|
176
|
-
}
|
339
|
+
// Precompute the overriding headers from the full request.
|
340
|
+
let overriding_headers = self.build_overriding_headers(&req, context);
|
177
341
|
|
178
|
-
//
|
179
|
-
|
180
|
-
if let Some(header_value) = header_value {
|
181
|
-
reqwest_builder =
|
182
|
-
reqwest_builder.header(name, header_value.to_string(&req, context));
|
183
|
-
}
|
184
|
-
}
|
342
|
+
// Determine max_attempts based on the number of backends.
|
343
|
+
let max_attempts = self.backends.len();
|
185
344
|
|
186
|
-
let
|
187
|
-
|
345
|
+
let reqwest_response_result = if is_idempotent(&req_info.method) {
|
346
|
+
let (_parts, body) = req.into_parts();
|
347
|
+
let replayable_bytes = match body.into_data_stream().try_collect::<Vec<Bytes>>().await {
|
348
|
+
Ok(chunks) => {
|
349
|
+
let mut buf = BytesMut::new();
|
350
|
+
for chunk in chunks {
|
351
|
+
buf.extend_from_slice(&chunk);
|
352
|
+
}
|
353
|
+
buf.freeze()
|
354
|
+
}
|
355
|
+
Err(e) => {
|
356
|
+
tracing::error!("Error buffering request body: {}", e);
|
357
|
+
return Ok(Either::Right(error_response));
|
358
|
+
}
|
359
|
+
};
|
360
|
+
self.send_request_idempotent(
|
361
|
+
&req_info,
|
362
|
+
&url,
|
363
|
+
host_str,
|
364
|
+
max_attempts,
|
365
|
+
replayable_bytes,
|
366
|
+
&overriding_headers,
|
367
|
+
)
|
368
|
+
.await
|
369
|
+
} else {
|
370
|
+
self.send_request_non_idempotent(req, &req_info, &url, host_str, &overriding_headers)
|
371
|
+
.await
|
372
|
+
};
|
188
373
|
|
189
|
-
let response = match
|
374
|
+
let response = match reqwest_response_result {
|
190
375
|
Ok(response) => {
|
191
376
|
let status = response.status();
|
192
377
|
let mut builder = Response::builder().status(status);
|
@@ -199,17 +384,30 @@ impl MiddlewareLayer for Proxy {
|
|
199
384
|
.map_ok(Frame::data)
|
200
385
|
.map_err(|_| -> Infallible { unreachable!("We handle IO errors above") }),
|
201
386
|
)));
|
202
|
-
|
203
|
-
response
|
204
|
-
} else {
|
205
|
-
error_response
|
206
|
-
}
|
387
|
+
response.unwrap_or(error_response)
|
207
388
|
}
|
208
389
|
Err(e) => {
|
209
|
-
|
210
|
-
|
390
|
+
if let Some(inner) = e.source() {
|
391
|
+
if inner.downcast_ref::<MaxBodySizeReached>().is_some() {
|
392
|
+
let mut max_body_response = Response::new(BoxBody::new(Empty::new()));
|
393
|
+
*max_body_response.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
|
394
|
+
return Ok(Either::Right(max_body_response));
|
395
|
+
}
|
396
|
+
}
|
397
|
+
if e.is_timeout() {
|
398
|
+
GATEWAY_TIMEOUT_RESPONSE.to_http_response(accept).await
|
399
|
+
} else if e.is_connect() {
|
400
|
+
BAD_GATEWAY_RESPONSE.to_http_response(accept).await
|
401
|
+
} else if e.is_status() {
|
402
|
+
SERVICE_UNAVAILABLE_RESPONSE.to_http_response(accept).await
|
403
|
+
} else {
|
404
|
+
INTERNAL_SERVER_ERROR_RESPONSE
|
405
|
+
.to_http_response(accept)
|
406
|
+
.await
|
407
|
+
}
|
211
408
|
}
|
212
409
|
};
|
410
|
+
|
213
411
|
Ok(Either::Right(response))
|
214
412
|
}
|
215
413
|
}
|
@@ -1,10 +1,8 @@
|
|
1
1
|
use super::{token_source::TokenSource, ErrorResponse, FromValue, MiddlewareLayer};
|
2
|
-
use crate::server::{
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
},
|
7
|
-
types::{HttpRequest, HttpResponse, RequestExt},
|
2
|
+
use crate::server::http_message_types::{HttpRequest, HttpResponse, RequestExt};
|
3
|
+
use crate::services::itsi_http_service::HttpRequestContext;
|
4
|
+
use crate::services::rate_limiter::{
|
5
|
+
create_rate_limit_key, get_rate_limiter, RateLimitError, RateLimiter, RateLimiterConfig,
|
8
6
|
};
|
9
7
|
use async_trait::async_trait;
|
10
8
|
use either::Either;
|
@@ -21,9 +19,14 @@ pub struct RateLimit {
|
|
21
19
|
#[serde(skip_deserializing)]
|
22
20
|
pub rate_limiter: OnceLock<Arc<dyn RateLimiter>>,
|
23
21
|
pub store_config: RateLimiterConfig,
|
22
|
+
#[serde(default = "too_many_requests_error_response")]
|
24
23
|
pub error_response: ErrorResponse,
|
25
24
|
}
|
26
25
|
|
26
|
+
fn too_many_requests_error_response() -> ErrorResponse {
|
27
|
+
ErrorResponse::too_many_requests()
|
28
|
+
}
|
29
|
+
|
27
30
|
#[derive(Debug, Clone, Deserialize)]
|
28
31
|
pub enum RateLimitKey {
|
29
32
|
#[serde(rename(deserialize = "address"))]
|
@@ -46,7 +49,7 @@ impl MiddlewareLayer for RateLimit {
|
|
46
49
|
async fn before(
|
47
50
|
&self,
|
48
51
|
req: HttpRequest,
|
49
|
-
context: &mut
|
52
|
+
context: &mut HttpRequestContext,
|
50
53
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
51
54
|
// Get the key to rate limit on
|
52
55
|
let key_value = match &self.key {
|
@@ -92,21 +95,25 @@ impl MiddlewareLayer for RateLimit {
|
|
92
95
|
let limit = self.requests;
|
93
96
|
|
94
97
|
match limiter.check_limit(&rate_limit_key, limit, timeout).await {
|
95
|
-
Ok(_) =>
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
98
|
+
Ok(_) => Ok(Either::Left(req)),
|
99
|
+
Err(RateLimitError::RateLimitExceeded { limit, ttl, .. }) => {
|
100
|
+
let mut response = self
|
101
|
+
.error_response
|
102
|
+
.to_http_response(req.accept().into())
|
103
|
+
.await;
|
104
|
+
response
|
105
|
+
.headers_mut()
|
106
|
+
.insert("X-RateLimit-Limit", limit.to_string().parse().unwrap());
|
107
|
+
response
|
108
|
+
.headers_mut()
|
109
|
+
.insert("X-RateLimit-Remaining", "0".parse().unwrap());
|
110
|
+
response
|
111
|
+
.headers_mut()
|
112
|
+
.insert("X-RateLimit-Reset", ttl.to_string().parse().unwrap());
|
113
|
+
response
|
114
|
+
.headers_mut()
|
115
|
+
.insert("Retry-After", ttl.to_string().parse().unwrap());
|
116
|
+
Ok(Either::Right(response))
|
110
117
|
}
|
111
118
|
Err(e) => {
|
112
119
|
// Other error, log and allow request (fail open)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
use crate::
|
2
|
-
|
3
|
-
|
1
|
+
use crate::{
|
2
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
3
|
+
services::itsi_http_service::HttpRequestContext,
|
4
4
|
};
|
5
5
|
|
6
6
|
use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
|
@@ -42,7 +42,7 @@ impl MiddlewareLayer for Redirect {
|
|
42
42
|
async fn before(
|
43
43
|
&self,
|
44
44
|
req: HttpRequest,
|
45
|
-
context: &mut
|
45
|
+
context: &mut HttpRequestContext,
|
46
46
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
47
47
|
Ok(Either::Right(self.redirect_response(&req, context)?))
|
48
48
|
}
|
@@ -52,7 +52,7 @@ impl Redirect {
|
|
52
52
|
pub fn redirect_response(
|
53
53
|
&self,
|
54
54
|
req: &HttpRequest,
|
55
|
-
context: &mut
|
55
|
+
context: &mut HttpRequestContext,
|
56
56
|
) -> Result<HttpResponse> {
|
57
57
|
let mut response = Response::new(BoxBody::new(Empty::new()));
|
58
58
|
*response.status_mut() = match self.redirect_type {
|
@@ -65,7 +65,7 @@ impl Redirect {
|
|
65
65
|
"Location",
|
66
66
|
self.to.rewrite_request(req, context).parse().map_err(|e| {
|
67
67
|
magnus::Error::new(
|
68
|
-
magnus::exception::
|
68
|
+
magnus::exception::standard_error(),
|
69
69
|
format!("Invalid Rewrite String: {:?}: {}", self.to, e),
|
70
70
|
)
|
71
71
|
})?,
|
@@ -1,10 +1,11 @@
|
|
1
1
|
use std::collections::HashMap;
|
2
2
|
|
3
|
-
use
|
4
|
-
|
5
|
-
|
6
|
-
types::{HttpRequest, HttpResponse},
|
3
|
+
use crate::{
|
4
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
5
|
+
services::itsi_http_service::HttpRequestContext,
|
7
6
|
};
|
7
|
+
|
8
|
+
use super::{FromValue, MiddlewareLayer};
|
8
9
|
use async_trait::async_trait;
|
9
10
|
use either::Either;
|
10
11
|
use http::HeaderName;
|
@@ -22,7 +23,7 @@ impl MiddlewareLayer for RequestHeaders {
|
|
22
23
|
async fn before(
|
23
24
|
&self,
|
24
25
|
mut req: HttpRequest,
|
25
|
-
_: &mut
|
26
|
+
_: &mut HttpRequestContext,
|
26
27
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
27
28
|
let headers = req.headers_mut();
|
28
29
|
for removal in &self.removals {
|
@@ -1,7 +1,9 @@
|
|
1
1
|
use std::collections::HashMap;
|
2
2
|
|
3
3
|
use super::{FromValue, MiddlewareLayer};
|
4
|
-
use crate::
|
4
|
+
use crate::{
|
5
|
+
server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
|
6
|
+
};
|
5
7
|
use async_trait::async_trait;
|
6
8
|
use http::HeaderName;
|
7
9
|
use serde::Deserialize;
|
@@ -14,7 +16,7 @@ pub struct ResponseHeaders {
|
|
14
16
|
|
15
17
|
#[async_trait]
|
16
18
|
impl MiddlewareLayer for ResponseHeaders {
|
17
|
-
async fn after(&self, mut resp: HttpResponse, _: &mut
|
19
|
+
async fn after(&self, mut resp: HttpResponse, _: &mut HttpRequestContext) -> HttpResponse {
|
18
20
|
let headers = resp.headers_mut();
|
19
21
|
for removal in &self.removals {
|
20
22
|
headers.remove(removal);
|