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,127 +1,157 @@
|
|
1
|
-
use crate::server::static_file_server::ROOT_STATIC_FILE_SERVER;
|
2
|
-
use crate::server::types::RequestExt;
|
3
|
-
use crate::server::{
|
4
|
-
itsi_service::RequestContext,
|
5
|
-
types::{HttpRequest, HttpResponse},
|
6
|
-
};
|
7
|
-
|
8
1
|
use bytes::Bytes;
|
9
|
-
use
|
2
|
+
use http::header::CONTENT_TYPE;
|
10
3
|
use http::Response;
|
11
4
|
use http_body_util::{combinators::BoxBody, Full};
|
12
|
-
use
|
13
|
-
use
|
14
|
-
use std::path::
|
5
|
+
use serde::{Deserialize, Deserializer};
|
6
|
+
use std::convert::Infallible;
|
7
|
+
use std::path::PathBuf;
|
8
|
+
|
9
|
+
use crate::server::http_message_types::{HttpResponse, ResponseFormat};
|
10
|
+
use crate::services::static_file_server::ROOT_STATIC_FILE_SERVER;
|
11
|
+
mod default_responses;
|
15
12
|
|
16
13
|
#[derive(Debug, Clone, Deserialize)]
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
pub struct ErrorResponse {
|
23
|
-
code: u16,
|
24
|
-
plaintext: Option<String>,
|
25
|
-
html: Option<PathBuf>,
|
26
|
-
json: Option<serde_json::Value>,
|
27
|
-
default: ErrorFormat,
|
14
|
+
pub enum ContentSource {
|
15
|
+
#[serde(rename(deserialize = "inline"))]
|
16
|
+
Inline(String),
|
17
|
+
#[serde(rename(deserialize = "file"))]
|
18
|
+
File(PathBuf),
|
28
19
|
}
|
29
20
|
|
30
21
|
#[derive(Debug, Clone, Deserialize, Default)]
|
31
|
-
enum
|
32
|
-
#[default]
|
22
|
+
pub enum DefaultFormat {
|
33
23
|
#[serde(rename(deserialize = "plaintext"))]
|
34
24
|
Plaintext,
|
25
|
+
#[default]
|
35
26
|
#[serde(rename(deserialize = "html"))]
|
36
27
|
Html,
|
37
28
|
#[serde(rename(deserialize = "json"))]
|
38
29
|
Json,
|
39
30
|
}
|
40
31
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
32
|
+
#[derive(Debug, Clone)]
|
33
|
+
pub struct ErrorResponse {
|
34
|
+
pub code: u16,
|
35
|
+
pub plaintext: Option<ContentSource>,
|
36
|
+
pub html: Option<ContentSource>,
|
37
|
+
pub json: Option<ContentSource>,
|
38
|
+
pub default: DefaultFormat, // must match one of the provided fields
|
39
|
+
}
|
40
|
+
|
41
|
+
impl<'de> Deserialize<'de> for ErrorResponse {
|
42
|
+
fn deserialize<D>(deserializer: D) -> Result<ErrorResponse, D::Error>
|
43
|
+
where
|
44
|
+
D: Deserializer<'de>,
|
45
|
+
{
|
46
|
+
let def = ErrorResponseDef::deserialize(deserializer)?;
|
47
|
+
Ok(def.into())
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
/// An untagged enum to support two input formats:
|
52
|
+
/// - A detailed struct with all fields.
|
53
|
+
/// - A string with the name of a default error response.
|
54
|
+
#[derive(Debug, Clone, Deserialize)]
|
55
|
+
#[serde(untagged)]
|
56
|
+
pub enum ErrorResponseDef {
|
57
|
+
Detailed {
|
58
|
+
code: u16,
|
59
|
+
plaintext: Option<ContentSource>,
|
60
|
+
html: Option<ContentSource>,
|
61
|
+
json: Option<ContentSource>,
|
62
|
+
default: DefaultFormat,
|
63
|
+
},
|
64
|
+
Named(String),
|
65
|
+
}
|
66
|
+
|
67
|
+
impl From<ErrorResponseDef> for ErrorResponse {
|
68
|
+
fn from(def: ErrorResponseDef) -> Self {
|
69
|
+
match def {
|
70
|
+
ErrorResponseDef::Detailed {
|
71
|
+
code,
|
72
|
+
plaintext,
|
73
|
+
html,
|
74
|
+
json,
|
75
|
+
default,
|
76
|
+
} => ErrorResponse {
|
77
|
+
code,
|
78
|
+
plaintext,
|
79
|
+
html,
|
80
|
+
json,
|
81
|
+
default,
|
82
|
+
},
|
83
|
+
ErrorResponseDef::Named(name) => match name.as_str() {
|
84
|
+
"internal_server_error" => ErrorResponse::internal_server_error(),
|
85
|
+
"not_found" => ErrorResponse::not_found(),
|
86
|
+
"unauthorized" => ErrorResponse::unauthorized(),
|
87
|
+
"forbidden" => ErrorResponse::forbidden(),
|
88
|
+
"payload_too_large" => ErrorResponse::payload_too_large(),
|
89
|
+
"too_many_requests" => ErrorResponse::too_many_requests(),
|
90
|
+
"bad_gateway" => ErrorResponse::bad_gateway(),
|
91
|
+
"service_unavailable" => ErrorResponse::service_unavailable(),
|
92
|
+
"gateway_timeout" => ErrorResponse::gateway_timeout(),
|
93
|
+
_ => panic!("Unknown error response name: {}", name),
|
94
|
+
},
|
49
95
|
}
|
50
96
|
}
|
51
97
|
}
|
52
98
|
|
53
99
|
impl ErrorResponse {
|
54
|
-
pub(crate) async fn to_http_response(&self,
|
55
|
-
let
|
56
|
-
let
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
Some(accept) if accept.contains("text/html") => {
|
61
|
-
if let Some(path) = &self.html {
|
62
|
-
let path = path.to_str().unwrap();
|
63
|
-
let response = ROOT_STATIC_FILE_SERVER.serve_single(path).await;
|
64
|
-
|
65
|
-
if response.status().is_success() {
|
66
|
-
response.into_body()
|
67
|
-
} else {
|
68
|
-
BoxBody::new(Full::new(Bytes::from("Error")))
|
69
|
-
}
|
70
|
-
} else {
|
71
|
-
BoxBody::new(Full::new(Bytes::from("Error")))
|
72
|
-
}
|
100
|
+
pub(crate) async fn to_http_response(&self, accept: ResponseFormat) -> HttpResponse {
|
101
|
+
let mut resp = Response::builder().status(self.code);
|
102
|
+
let response = match accept {
|
103
|
+
ResponseFormat::TEXT => {
|
104
|
+
resp = resp.header(CONTENT_TYPE, "text/plain");
|
105
|
+
resp.body(Self::get_response_body(self.code, &self.plaintext, accept).await)
|
73
106
|
}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
.as_ref()
|
78
|
-
.map(|json| json.to_string())
|
79
|
-
.unwrap_or_else(|| "Error".to_owned()),
|
80
|
-
)))
|
107
|
+
ResponseFormat::HTML => {
|
108
|
+
resp = resp.header(CONTENT_TYPE, "text/html");
|
109
|
+
resp.body(Self::get_response_body(self.code, &self.html, accept).await)
|
81
110
|
}
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
}
|
111
|
+
ResponseFormat::JSON => {
|
112
|
+
resp = resp.header(CONTENT_TYPE, "application/json");
|
113
|
+
resp.body(Self::get_response_body(self.code, &self.json, accept).await)
|
114
|
+
}
|
115
|
+
ResponseFormat::UNKNOWN => match self.default {
|
116
|
+
DefaultFormat::Plaintext => {
|
117
|
+
resp = resp.header(CONTENT_TYPE, "text/plain");
|
118
|
+
resp.body(Self::get_response_body(self.code, &self.plaintext, accept).await)
|
119
|
+
}
|
120
|
+
DefaultFormat::Html => {
|
121
|
+
resp = resp.header(CONTENT_TYPE, "text/html");
|
122
|
+
resp.body(Self::get_response_body(self.code, &self.html, accept).await)
|
123
|
+
}
|
124
|
+
DefaultFormat::Json => {
|
125
|
+
resp = resp.header(CONTENT_TYPE, "application/json");
|
126
|
+
resp.body(Self::get_response_body(self.code, &self.json, accept).await)
|
99
127
|
}
|
100
|
-
ErrorFormat::Json => BoxBody::new(Full::new(Bytes::from(
|
101
|
-
self.json
|
102
|
-
.as_ref()
|
103
|
-
.map(|json| json.to_string())
|
104
|
-
.unwrap_or_else(|| "Error".to_owned()),
|
105
|
-
))),
|
106
128
|
},
|
107
129
|
};
|
108
|
-
|
109
|
-
Response::builder().status(self.code).body(body).unwrap()
|
130
|
+
response.unwrap()
|
110
131
|
}
|
111
132
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
) ->
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
133
|
+
async fn get_response_body(
|
134
|
+
code: u16,
|
135
|
+
source: &Option<ContentSource>,
|
136
|
+
accept: ResponseFormat,
|
137
|
+
) -> BoxBody<Bytes, Infallible> {
|
138
|
+
match source {
|
139
|
+
Some(ContentSource::Inline(text)) => {
|
140
|
+
return BoxBody::new(Full::new(Bytes::from(text.clone())));
|
141
|
+
}
|
142
|
+
Some(ContentSource::File(path)) => {
|
143
|
+
// Convert the PathBuf to a &str (assumes valid UTF-8).
|
144
|
+
if let Some(path_str) = path.to_str() {
|
145
|
+
let response = ROOT_STATIC_FILE_SERVER
|
146
|
+
.serve_single(path_str, accept.clone(), &[])
|
147
|
+
.await;
|
148
|
+
if response.status().is_success() {
|
149
|
+
return response.into_body();
|
150
|
+
}
|
151
|
+
}
|
123
152
|
}
|
153
|
+
None => {}
|
124
154
|
}
|
125
|
-
|
155
|
+
ErrorResponse::fallback_body_for(code, accept)
|
126
156
|
}
|
127
157
|
}
|
@@ -1,5 +1,9 @@
|
|
1
|
+
use crate::{
|
2
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
3
|
+
services::itsi_http_service::HttpRequestContext,
|
4
|
+
};
|
5
|
+
|
1
6
|
use super::{FromValue, MiddlewareLayer};
|
2
|
-
use crate::server::{itsi_service::RequestContext, types::HttpResponse};
|
3
7
|
use async_trait::async_trait;
|
4
8
|
use base64::{engine::general_purpose, Engine as _};
|
5
9
|
use bytes::{Bytes, BytesMut};
|
@@ -50,9 +54,9 @@ fn default_true() -> bool {
|
|
50
54
|
impl MiddlewareLayer for ETag {
|
51
55
|
async fn before(
|
52
56
|
&self,
|
53
|
-
req:
|
54
|
-
context: &mut
|
55
|
-
) -> Result<Either<
|
57
|
+
req: HttpRequest,
|
58
|
+
context: &mut HttpRequestContext,
|
59
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
56
60
|
// Store if-none-match header in context if present for later use in after hook
|
57
61
|
if self.handle_if_none_match {
|
58
62
|
if let Some(if_none_match) = req.headers().get(header::IF_NONE_MATCH) {
|
@@ -64,7 +68,7 @@ impl MiddlewareLayer for ETag {
|
|
64
68
|
Ok(Either::Left(req))
|
65
69
|
}
|
66
70
|
|
67
|
-
async fn after(&self, resp: HttpResponse, context: &mut
|
71
|
+
async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
|
68
72
|
// Skip for error responses or responses that shouldn't have ETags
|
69
73
|
match resp.status() {
|
70
74
|
StatusCode::OK
|
@@ -3,10 +3,7 @@ use http::{header::GetAll, HeaderValue};
|
|
3
3
|
/// Given a list of header values (which may be comma-separated and may have quality parameters)
|
4
4
|
/// and a list of supported items (each supported item is a full value or a prefix ending with '*'),
|
5
5
|
/// return Some(supported_item) for the first supported item that matches any header value, or None.
|
6
|
-
pub fn find_first_supported<'a, I>(
|
7
|
-
header_values: &http::header::GetAll<http::HeaderValue>,
|
8
|
-
supported: I,
|
9
|
-
) -> Option<&'a str>
|
6
|
+
pub fn find_first_supported<'a, I>(header_values: &[HeaderValue], supported: I) -> Option<&'a str>
|
10
7
|
where
|
11
8
|
I: IntoIterator<Item = &'a str> + Clone,
|
12
9
|
{
|
@@ -1,9 +1,11 @@
|
|
1
|
-
use
|
2
|
-
use crate::
|
3
|
-
|
4
|
-
|
5
|
-
types::{HttpRequest, HttpResponse, RequestExt},
|
1
|
+
use crate::server::http_message_types::{HttpRequest, HttpResponse, RequestExt};
|
2
|
+
use crate::services::itsi_http_service::HttpRequestContext;
|
3
|
+
use crate::services::rate_limiter::{
|
4
|
+
get_ban_manager, get_rate_limiter, BanManager, RateLimiter, RateLimiterConfig,
|
6
5
|
};
|
6
|
+
|
7
|
+
use super::{ErrorResponse, FromValue, MiddlewareLayer};
|
8
|
+
|
7
9
|
use async_trait::async_trait;
|
8
10
|
use either::Either;
|
9
11
|
use itsi_tracing::*;
|
@@ -32,9 +34,14 @@ pub struct IntrusionProtection {
|
|
32
34
|
#[serde(skip_deserializing)]
|
33
35
|
pub ban_manager: OnceLock<BanManager>,
|
34
36
|
pub store_config: RateLimiterConfig,
|
37
|
+
#[serde(default = "forbidden_error_response")]
|
35
38
|
pub error_response: ErrorResponse,
|
36
39
|
}
|
37
40
|
|
41
|
+
fn forbidden_error_response() -> ErrorResponse {
|
42
|
+
ErrorResponse::forbidden()
|
43
|
+
}
|
44
|
+
|
38
45
|
#[async_trait]
|
39
46
|
impl MiddlewareLayer for IntrusionProtection {
|
40
47
|
async fn initialize(&self) -> Result<()> {
|
@@ -89,7 +96,7 @@ impl MiddlewareLayer for IntrusionProtection {
|
|
89
96
|
async fn before(
|
90
97
|
&self,
|
91
98
|
req: HttpRequest,
|
92
|
-
context: &mut
|
99
|
+
context: &mut HttpRequestContext,
|
93
100
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
94
101
|
// Get client IP address from context's service
|
95
102
|
let client_ip = &context.addr;
|
@@ -97,10 +104,11 @@ impl MiddlewareLayer for IntrusionProtection {
|
|
97
104
|
// Check if the IP is already banned
|
98
105
|
if let Some(ban_manager) = self.ban_manager.get() {
|
99
106
|
match ban_manager.is_banned(client_ip).await {
|
100
|
-
Ok(Some(
|
101
|
-
info!("Request from banned IP {}: {}", client_ip, reason);
|
107
|
+
Ok(Some(_)) => {
|
102
108
|
return Ok(Either::Right(
|
103
|
-
self.error_response
|
109
|
+
self.error_response
|
110
|
+
.to_http_response(req.accept().into())
|
111
|
+
.await,
|
104
112
|
));
|
105
113
|
}
|
106
114
|
Err(e) => {
|
@@ -118,11 +126,8 @@ impl MiddlewareLayer for IntrusionProtection {
|
|
118
126
|
// Check for banned URL patterns
|
119
127
|
if let Some(url_matcher) = self.banned_url_pattern_matcher.get() {
|
120
128
|
let path = req.uri().path_and_query().map(|p| p.as_str()).unwrap_or("");
|
121
|
-
info!("Checking URL pattern match for {}", path);
|
122
|
-
if url_matcher.is_match(path) {
|
123
|
-
info!("Intrusion detected: URL pattern match for {}", path);
|
124
129
|
|
125
|
-
|
130
|
+
if url_matcher.is_match(path) {
|
126
131
|
if let Some(ban_manager) = self.ban_manager.get() {
|
127
132
|
match ban_manager
|
128
133
|
.ban_ip(
|
@@ -132,17 +137,16 @@ impl MiddlewareLayer for IntrusionProtection {
|
|
132
137
|
)
|
133
138
|
.await
|
134
139
|
{
|
135
|
-
Ok(_) =>
|
136
|
-
"Successfully banned IP {} for {} seconds",
|
137
|
-
client_ip, self.banned_time_seconds
|
138
|
-
),
|
140
|
+
Ok(_) => {}
|
139
141
|
Err(e) => error!("Failed to ban IP {}: {:?}", client_ip, e),
|
140
142
|
}
|
141
143
|
}
|
142
144
|
|
143
145
|
// Always return the error response even if banning failed
|
144
146
|
return Ok(Either::Right(
|
145
|
-
self.error_response
|
147
|
+
self.error_response
|
148
|
+
.to_http_response(req.accept().into())
|
149
|
+
.await,
|
146
150
|
));
|
147
151
|
}
|
148
152
|
}
|
@@ -180,7 +184,9 @@ impl MiddlewareLayer for IntrusionProtection {
|
|
180
184
|
|
181
185
|
// Always return the error response even if banning failed
|
182
186
|
return Ok(Either::Right(
|
183
|
-
self.error_response
|
187
|
+
self.error_response
|
188
|
+
.to_http_response(req.accept().into())
|
189
|
+
.await,
|
184
190
|
));
|
185
191
|
}
|
186
192
|
}
|
@@ -4,8 +4,8 @@ use itsi_tracing::*;
|
|
4
4
|
use magnus::error::Result;
|
5
5
|
use serde::Deserialize;
|
6
6
|
|
7
|
-
use crate::server::
|
8
|
-
use crate::
|
7
|
+
use crate::server::http_message_types::{HttpRequest, HttpResponse};
|
8
|
+
use crate::services::itsi_http_service::HttpRequestContext;
|
9
9
|
|
10
10
|
use super::string_rewrite::StringRewrite;
|
11
11
|
use super::{FromValue, MiddlewareLayer};
|
@@ -60,7 +60,7 @@ impl MiddlewareLayer for LogRequests {
|
|
60
60
|
async fn before(
|
61
61
|
&self,
|
62
62
|
req: HttpRequest,
|
63
|
-
context: &mut
|
63
|
+
context: &mut HttpRequestContext,
|
64
64
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
65
65
|
context.track_start_time();
|
66
66
|
if let Some(LogConfig { level, format }) = self.before.as_ref() {
|
@@ -70,7 +70,7 @@ impl MiddlewareLayer for LogRequests {
|
|
70
70
|
Ok(Either::Left(req))
|
71
71
|
}
|
72
72
|
|
73
|
-
async fn after(&self, resp: HttpResponse, context: &mut
|
73
|
+
async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
|
74
74
|
if let Some(LogConfig { level, format }) = self.after.as_ref() {
|
75
75
|
level.log(format.rewrite_response(&resp, context));
|
76
76
|
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
use crate::{
|
2
|
+
server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
|
3
|
+
services::itsi_http_service::HttpRequestContext,
|
4
|
+
};
|
5
|
+
|
6
|
+
use super::{ErrorResponse, FromValue, MiddlewareLayer};
|
7
|
+
use async_trait::async_trait;
|
8
|
+
use either::Either;
|
9
|
+
use http::StatusCode;
|
10
|
+
use magnus::error::Result;
|
11
|
+
use serde::Deserialize;
|
12
|
+
use std::sync::atomic::Ordering;
|
13
|
+
|
14
|
+
#[derive(Debug, Clone, Deserialize)]
|
15
|
+
pub struct MaxBody {
|
16
|
+
pub max_size: usize,
|
17
|
+
#[serde(default = "payload_too_large_error_response")]
|
18
|
+
pub error_response: ErrorResponse,
|
19
|
+
}
|
20
|
+
|
21
|
+
fn payload_too_large_error_response() -> ErrorResponse {
|
22
|
+
ErrorResponse::payload_too_large()
|
23
|
+
}
|
24
|
+
|
25
|
+
#[async_trait]
|
26
|
+
impl MiddlewareLayer for MaxBody {
|
27
|
+
async fn before(
|
28
|
+
&self,
|
29
|
+
req: HttpRequest,
|
30
|
+
context: &mut HttpRequestContext,
|
31
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
32
|
+
req.body().limit.store(self.max_size, Ordering::Relaxed);
|
33
|
+
context.set_response_format(req.accept().into());
|
34
|
+
Ok(Either::Left(req))
|
35
|
+
}
|
36
|
+
|
37
|
+
async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
|
38
|
+
if resp.status() == StatusCode::PAYLOAD_TOO_LARGE {
|
39
|
+
self.error_response
|
40
|
+
.to_http_response(context.response_format().clone())
|
41
|
+
.await
|
42
|
+
} else {
|
43
|
+
resp
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
impl FromValue for MaxBody {}
|
@@ -11,6 +11,7 @@ mod etag;
|
|
11
11
|
mod header_interpretation;
|
12
12
|
mod intrusion_protection;
|
13
13
|
mod log_requests;
|
14
|
+
mod max_body;
|
14
15
|
mod proxy;
|
15
16
|
mod rate_limit;
|
16
17
|
mod redirect;
|
@@ -18,6 +19,7 @@ mod request_headers;
|
|
18
19
|
mod response_headers;
|
19
20
|
mod ruby_app;
|
20
21
|
mod static_assets;
|
22
|
+
mod static_response;
|
21
23
|
mod string_rewrite;
|
22
24
|
mod token_source;
|
23
25
|
|
@@ -38,6 +40,7 @@ pub use intrusion_protection::IntrusionProtection;
|
|
38
40
|
pub use log_requests::LogRequests;
|
39
41
|
use magnus::error::Result;
|
40
42
|
use magnus::Value;
|
43
|
+
pub use max_body::MaxBody;
|
41
44
|
pub use proxy::Proxy;
|
42
45
|
pub use rate_limit::RateLimit;
|
43
46
|
pub use redirect::Redirect;
|
@@ -47,9 +50,11 @@ pub use ruby_app::RubyApp;
|
|
47
50
|
use serde::Deserialize;
|
48
51
|
use serde_magnus::deserialize;
|
49
52
|
pub use static_assets::StaticAssets;
|
53
|
+
pub use static_response::StaticResponse;
|
50
54
|
|
51
|
-
use crate::server::
|
52
|
-
use crate::server::
|
55
|
+
use crate::server::http_message_types::HttpRequest;
|
56
|
+
use crate::server::http_message_types::HttpResponse;
|
57
|
+
use crate::services::itsi_http_service::HttpRequestContext;
|
53
58
|
|
54
59
|
pub trait FromValue: Sized + Send + Sync + 'static {
|
55
60
|
fn from_value(value: Value) -> Result<Self>
|
@@ -70,13 +75,13 @@ pub trait MiddlewareLayer: Sized + Send + Sync + 'static {
|
|
70
75
|
async fn before(
|
71
76
|
&self,
|
72
77
|
req: HttpRequest,
|
73
|
-
_context: &mut
|
78
|
+
_context: &mut HttpRequestContext,
|
74
79
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
75
80
|
Ok(Either::Left(req))
|
76
81
|
}
|
77
82
|
|
78
83
|
/// The "after" hook. By default, it passes through the response.
|
79
|
-
async fn after(&self, resp: HttpResponse, _context: &mut
|
84
|
+
async fn after(&self, resp: HttpResponse, _context: &mut HttpRequestContext) -> HttpResponse {
|
80
85
|
resp
|
81
86
|
}
|
82
87
|
}
|