itsi 0.1.14 → 0.1.19
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 +126 -272
- 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 +12 -11
- 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 +126 -272
- 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 +117 -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 +171 -99
- 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 +327 -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,5 +1,8 @@
|
|
1
|
+
use crate::{
|
2
|
+
server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
|
3
|
+
};
|
4
|
+
|
1
5
|
use super::{FromValue, MiddlewareLayer};
|
2
|
-
use crate::server::{itsi_service::RequestContext, types::HttpResponse};
|
3
6
|
use async_trait::async_trait;
|
4
7
|
use http::{HeaderName, HeaderValue};
|
5
8
|
use magnus::error::Result;
|
@@ -90,7 +93,7 @@ impl MiddlewareLayer for CacheControl {
|
|
90
93
|
Ok(())
|
91
94
|
}
|
92
95
|
|
93
|
-
async fn after(&self, mut resp: HttpResponse, _: &mut
|
96
|
+
async fn after(&self, mut resp: HttpResponse, _: &mut HttpRequestContext) -> HttpResponse {
|
94
97
|
// Skip for statuses where caching doesn't make sense
|
95
98
|
let status = resp.status().as_u16();
|
96
99
|
if matches!(status, 401 | 403 | 500..=599) {
|
@@ -1,26 +1,25 @@
|
|
1
|
+
use crate::{
|
2
|
+
server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
|
3
|
+
};
|
4
|
+
|
1
5
|
use super::{
|
2
6
|
header_interpretation::{find_first_supported, header_contains},
|
3
7
|
FromValue, MiddlewareLayer,
|
4
8
|
};
|
5
|
-
|
6
|
-
itsi_service::RequestContext,
|
7
|
-
types::{HttpRequest, HttpResponse},
|
8
|
-
};
|
9
|
+
|
9
10
|
use async_compression::{
|
10
11
|
tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder},
|
11
12
|
Level,
|
12
13
|
};
|
13
14
|
use async_trait::async_trait;
|
14
15
|
use bytes::{Bytes, BytesMut};
|
15
|
-
use either::Either;
|
16
16
|
use futures::TryStreamExt;
|
17
17
|
use http::{
|
18
|
-
header::{GetAll,
|
18
|
+
header::{GetAll, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE},
|
19
19
|
HeaderValue, Response,
|
20
20
|
};
|
21
21
|
use http_body_util::{combinators::BoxBody, BodyExt, Full, StreamBody};
|
22
22
|
use hyper::body::{Body, Frame};
|
23
|
-
use magnus::error::Result;
|
24
23
|
use serde::{Deserialize, Serialize};
|
25
24
|
use std::convert::Infallible;
|
26
25
|
use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
|
@@ -148,55 +147,16 @@ fn update_content_encoding(parts: &mut http::response::Parts, new_encoding: Head
|
|
148
147
|
|
149
148
|
#[async_trait]
|
150
149
|
impl MiddlewareLayer for Compression {
|
151
|
-
/// A the request comes in, take note of the accepted content encodings,
|
152
|
-
/// so that we can apply compression on the response, where appropriate.
|
153
|
-
///
|
154
|
-
/// We store the temporary state inside the RequestContext.
|
155
|
-
async fn before(
|
156
|
-
&self,
|
157
|
-
req: HttpRequest,
|
158
|
-
context: &mut RequestContext,
|
159
|
-
) -> Result<Either<HttpRequest, HttpResponse>> {
|
160
|
-
let algo = match find_first_supported(
|
161
|
-
&req.headers().get_all(ACCEPT_ENCODING),
|
162
|
-
self.algorithms.iter().map(|algo| algo.as_str()),
|
163
|
-
) {
|
164
|
-
Some("gzip") => CompressionAlgorithm::Gzip,
|
165
|
-
Some("br") => CompressionAlgorithm::Brotli,
|
166
|
-
Some("deflate") => CompressionAlgorithm::Deflate,
|
167
|
-
Some("zstd") => CompressionAlgorithm::Zstd,
|
168
|
-
_ => CompressionAlgorithm::None,
|
169
|
-
};
|
170
|
-
|
171
|
-
if matches!(algo, CompressionAlgorithm::None) {
|
172
|
-
return Ok(Either::Left(req));
|
173
|
-
}
|
174
|
-
|
175
|
-
context.set_compression_method(algo);
|
176
|
-
|
177
|
-
Ok(Either::Left(req))
|
178
|
-
}
|
179
|
-
|
180
150
|
/// We'll apply compression on the response, where appropriate.
|
181
151
|
/// This is if:
|
182
152
|
/// * The response body is larger than the minimum size.
|
183
153
|
/// * The response content type is supported.
|
184
154
|
/// * The client supports the compression algorithm.
|
185
|
-
async fn after(&self, resp: HttpResponse, context: &mut
|
186
|
-
let compression_method;
|
187
|
-
if let Some(method) = context.compression_method.get() {
|
188
|
-
compression_method = method.clone();
|
189
|
-
} else {
|
190
|
-
return resp;
|
191
|
-
}
|
192
|
-
|
193
|
-
if matches!(compression_method, CompressionAlgorithm::None) {
|
194
|
-
return resp;
|
195
|
-
}
|
196
|
-
|
155
|
+
async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
|
197
156
|
let body_size = resp.size_hint().exact();
|
198
157
|
let resp = resp;
|
199
158
|
|
159
|
+
// Don't compress if it's not an explicitly listed compressable type
|
200
160
|
if !self
|
201
161
|
.mime_types
|
202
162
|
.iter()
|
@@ -205,14 +165,43 @@ impl MiddlewareLayer for Compression {
|
|
205
165
|
return resp;
|
206
166
|
}
|
207
167
|
|
168
|
+
// Don't compress streams unless compress streams is enabled.
|
208
169
|
if body_size.is_none() && !self.compress_streams {
|
209
170
|
return resp;
|
210
171
|
}
|
211
172
|
|
173
|
+
// Don't compress too small bodies
|
212
174
|
if body_size.is_some_and(|s| s < self.min_size as u64) {
|
213
175
|
return resp;
|
214
176
|
}
|
215
177
|
|
178
|
+
// Don't recompress if we're already compressed in a supported format
|
179
|
+
for existing_encoding in resp.headers().get_all(CONTENT_ENCODING) {
|
180
|
+
if let Ok(encodings) = existing_encoding.to_str() {
|
181
|
+
for encoding in encodings.split(',').map(str::trim) {
|
182
|
+
let encoding = encoding.split(';').next().unwrap_or(encoding).trim();
|
183
|
+
if self.algorithms.iter().any(|algo| algo.as_str() == encoding) {
|
184
|
+
return resp;
|
185
|
+
}
|
186
|
+
}
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
let compression_method = match find_first_supported(
|
191
|
+
&context.supported_encoding_set,
|
192
|
+
self.algorithms.iter().map(|algo| algo.as_str()),
|
193
|
+
) {
|
194
|
+
Some("gzip") => CompressionAlgorithm::Gzip,
|
195
|
+
Some("br") => CompressionAlgorithm::Brotli,
|
196
|
+
Some("deflate") => CompressionAlgorithm::Deflate,
|
197
|
+
Some("zstd") => CompressionAlgorithm::Zstd,
|
198
|
+
_ => CompressionAlgorithm::None,
|
199
|
+
};
|
200
|
+
|
201
|
+
if matches!(compression_method, CompressionAlgorithm::None) {
|
202
|
+
return resp;
|
203
|
+
}
|
204
|
+
|
216
205
|
let (mut parts, body) = resp.into_parts();
|
217
206
|
|
218
207
|
let new_body = if let Some(_size) = body_size {
|
@@ -1,8 +1,9 @@
|
|
1
1
|
use super::{FromValue, MiddlewareLayer};
|
2
|
-
use crate::
|
3
|
-
|
4
|
-
|
2
|
+
use crate::{
|
3
|
+
server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
|
4
|
+
services::itsi_http_service::HttpRequestContext,
|
5
5
|
};
|
6
|
+
|
6
7
|
use async_trait::async_trait;
|
7
8
|
use http::{HeaderMap, Method, Response};
|
8
9
|
use http_body_util::{combinators::BoxBody, Empty};
|
@@ -69,14 +70,14 @@ impl Cors {
|
|
69
70
|
fn cors_headers(&self, origin: &str) -> Result<HeaderMap> {
|
70
71
|
let mut headers = HeaderMap::new();
|
71
72
|
|
72
|
-
headers.insert("Vary", "Origin".parse().map_err(ItsiError::
|
73
|
+
headers.insert("Vary", "Origin".parse().map_err(ItsiError::new)?);
|
73
74
|
|
74
75
|
if origin.is_empty() {
|
75
76
|
// When credentials are allowed, you cannot return "*".
|
76
77
|
if !self.allow_credentials {
|
77
78
|
headers.insert(
|
78
79
|
"Access-Control-Allow-Origin",
|
79
|
-
"*".parse().map_err(ItsiError::
|
80
|
+
"*".parse().map_err(ItsiError::new)?,
|
80
81
|
);
|
81
82
|
}
|
82
83
|
return Ok(headers);
|
@@ -97,7 +98,7 @@ impl Cors {
|
|
97
98
|
};
|
98
99
|
headers.insert(
|
99
100
|
"Access-Control-Allow-Origin",
|
100
|
-
value.parse().map_err(ItsiError::
|
101
|
+
value.parse().map_err(ItsiError::new)?,
|
101
102
|
);
|
102
103
|
}
|
103
104
|
|
@@ -110,7 +111,7 @@ impl Cors {
|
|
110
111
|
.collect::<Vec<&str>>()
|
111
112
|
.join(", ")
|
112
113
|
.parse()
|
113
|
-
.map_err(ItsiError::
|
114
|
+
.map_err(ItsiError::new)?,
|
114
115
|
);
|
115
116
|
}
|
116
117
|
if !self.allowed_headers.is_empty() {
|
@@ -119,19 +120,19 @@ impl Cors {
|
|
119
120
|
self.allowed_headers
|
120
121
|
.join(", ")
|
121
122
|
.parse()
|
122
|
-
.map_err(ItsiError::
|
123
|
+
.map_err(ItsiError::new)?,
|
123
124
|
);
|
124
125
|
}
|
125
126
|
if self.allow_credentials {
|
126
127
|
headers.insert(
|
127
128
|
"Access-Control-Allow-Credentials",
|
128
|
-
"true".parse().map_err(ItsiError::
|
129
|
+
"true".parse().map_err(ItsiError::new)?,
|
129
130
|
);
|
130
131
|
}
|
131
132
|
if let Some(max_age) = self.max_age {
|
132
133
|
headers.insert(
|
133
134
|
"Access-Control-Max-Age",
|
134
|
-
max_age.to_string().parse().map_err(ItsiError::
|
135
|
+
max_age.to_string().parse().map_err(ItsiError::new)?,
|
135
136
|
);
|
136
137
|
}
|
137
138
|
if !self.exposed_headers.is_empty() {
|
@@ -140,7 +141,7 @@ impl Cors {
|
|
140
141
|
self.exposed_headers
|
141
142
|
.join(", ")
|
142
143
|
.parse()
|
143
|
-
.map_err(ItsiError::
|
144
|
+
.map_err(ItsiError::new)?,
|
144
145
|
);
|
145
146
|
}
|
146
147
|
Ok(headers)
|
@@ -154,7 +155,7 @@ impl Cors {
|
|
154
155
|
) -> Result<HeaderMap> {
|
155
156
|
let mut headers = HeaderMap::new();
|
156
157
|
|
157
|
-
headers.insert("Vary", "Origin".parse().map_err(ItsiError::
|
158
|
+
headers.insert("Vary", "Origin".parse().map_err(ItsiError::new)?);
|
158
159
|
|
159
160
|
let origin = match origin {
|
160
161
|
Some(o) if !o.is_empty() => o,
|
@@ -208,25 +209,25 @@ impl Cors {
|
|
208
209
|
.collect::<Vec<&str>>()
|
209
210
|
.join(", ")
|
210
211
|
.parse()
|
211
|
-
.map_err(ItsiError::
|
212
|
+
.map_err(ItsiError::new)?,
|
212
213
|
);
|
213
214
|
headers.insert(
|
214
215
|
"Access-Control-Allow-Headers",
|
215
216
|
self.allowed_headers
|
216
217
|
.join(", ")
|
217
218
|
.parse()
|
218
|
-
.map_err(ItsiError::
|
219
|
+
.map_err(ItsiError::new)?,
|
219
220
|
);
|
220
221
|
if self.allow_credentials {
|
221
222
|
headers.insert(
|
222
223
|
"Access-Control-Allow-Credentials",
|
223
|
-
"true".parse().map_err(ItsiError::
|
224
|
+
"true".parse().map_err(ItsiError::new)?,
|
224
225
|
);
|
225
226
|
}
|
226
227
|
if let Some(max_age) = self.max_age {
|
227
228
|
headers.insert(
|
228
229
|
"Access-Control-Max-Age",
|
229
|
-
max_age.to_string().parse().map_err(ItsiError::
|
230
|
+
max_age.to_string().parse().map_err(ItsiError::new)?,
|
230
231
|
);
|
231
232
|
}
|
232
233
|
if !self.exposed_headers.is_empty() {
|
@@ -235,7 +236,7 @@ impl Cors {
|
|
235
236
|
self.exposed_headers
|
236
237
|
.join(", ")
|
237
238
|
.parse()
|
238
|
-
.map_err(ItsiError::
|
239
|
+
.map_err(ItsiError::new)?,
|
239
240
|
);
|
240
241
|
}
|
241
242
|
|
@@ -253,7 +254,7 @@ impl MiddlewareLayer for Cors {
|
|
253
254
|
async fn before(
|
254
255
|
&self,
|
255
256
|
req: HttpRequest,
|
256
|
-
context: &mut
|
257
|
+
context: &mut HttpRequestContext,
|
257
258
|
) -> Result<either::Either<HttpRequest, HttpResponse>> {
|
258
259
|
let origin = req.header("Origin");
|
259
260
|
if req.method() == Method::OPTIONS {
|
@@ -265,7 +266,7 @@ impl MiddlewareLayer for Cors {
|
|
265
266
|
*response_builder.headers_mut().unwrap() = headers;
|
266
267
|
let response = response_builder
|
267
268
|
.body(BoxBody::new(Empty::new()))
|
268
|
-
.map_err(ItsiError::
|
269
|
+
.map_err(ItsiError::new)?;
|
269
270
|
return Ok(either::Either::Right(response));
|
270
271
|
}
|
271
272
|
context.set_origin(origin.map(|s| s.to_string()));
|
@@ -273,7 +274,11 @@ impl MiddlewareLayer for Cors {
|
|
273
274
|
}
|
274
275
|
|
275
276
|
// The after hook can be used to inject CORS headers into non-preflight responses.
|
276
|
-
async fn after(
|
277
|
+
async fn after(
|
278
|
+
&self,
|
279
|
+
mut resp: HttpResponse,
|
280
|
+
context: &mut HttpRequestContext,
|
281
|
+
) -> HttpResponse {
|
277
282
|
if let Some(Some(origin)) = context.origin.get() {
|
278
283
|
if let Ok(cors_headers) = self.cors_headers(origin) {
|
279
284
|
for (key, value) in cors_headers.iter() {
|
@@ -1,6 +1,6 @@
|
|
1
|
-
use crate::
|
2
|
-
|
3
|
-
|
1
|
+
use crate::{
|
2
|
+
server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
|
3
|
+
services::itsi_http_service::HttpRequestContext,
|
4
4
|
};
|
5
5
|
|
6
6
|
use super::{ErrorResponse, FromValue, MiddlewareLayer};
|
@@ -17,28 +17,35 @@ pub struct DenyList {
|
|
17
17
|
#[serde(skip_deserializing)]
|
18
18
|
pub denied_ips: OnceLock<RegexSet>,
|
19
19
|
pub denied_patterns: Vec<String>,
|
20
|
+
#[serde(default = "forbidden_error_response")]
|
20
21
|
pub error_response: ErrorResponse,
|
21
22
|
}
|
22
23
|
|
24
|
+
fn forbidden_error_response() -> ErrorResponse {
|
25
|
+
ErrorResponse::forbidden()
|
26
|
+
}
|
27
|
+
|
23
28
|
#[async_trait]
|
24
29
|
impl MiddlewareLayer for DenyList {
|
25
30
|
async fn initialize(&self) -> Result<()> {
|
26
|
-
let denied_ips = RegexSet::new(&self.denied_patterns).map_err(ItsiError::
|
31
|
+
let denied_ips = RegexSet::new(&self.denied_patterns).map_err(ItsiError::new)?;
|
27
32
|
self.denied_ips
|
28
33
|
.set(denied_ips)
|
29
|
-
.map_err(|e| ItsiError::
|
34
|
+
.map_err(|e| ItsiError::new(format!("Failed to set allowed IPs: {:?}", e)))?;
|
30
35
|
Ok(())
|
31
36
|
}
|
32
37
|
|
33
38
|
async fn before(
|
34
39
|
&self,
|
35
40
|
req: HttpRequest,
|
36
|
-
context: &mut
|
41
|
+
context: &mut HttpRequestContext,
|
37
42
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
38
43
|
if let Some(denied_ips) = self.denied_ips.get() {
|
39
44
|
if denied_ips.is_match(&context.addr) {
|
40
45
|
return Ok(Either::Right(
|
41
|
-
self.error_response
|
46
|
+
self.error_response
|
47
|
+
.to_http_response(req.accept().into())
|
48
|
+
.await,
|
42
49
|
));
|
43
50
|
}
|
44
51
|
}
|
data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
use std::convert::Infallible;
|
2
|
+
|
3
|
+
use bytes::Bytes;
|
4
|
+
use http_body_util::{combinators::BoxBody, Full};
|
5
|
+
|
6
|
+
use crate::server::http_message_types::ResponseFormat;
|
7
|
+
|
8
|
+
use super::{ContentSource, DefaultFormat, ErrorResponse};
|
9
|
+
|
10
|
+
impl DefaultFormat {
|
11
|
+
pub fn response_for_code(&self, code: u16) -> ContentSource {
|
12
|
+
match self {
|
13
|
+
DefaultFormat::Plaintext => match code {
|
14
|
+
500 => ContentSource::Inline("500 Internal Error".to_owned()),
|
15
|
+
404 => ContentSource::Inline("404 Not Found".to_owned()),
|
16
|
+
401 => ContentSource::Inline("401 Unauthorized".to_owned()),
|
17
|
+
403 => ContentSource::Inline("403 Forbidden".to_owned()),
|
18
|
+
413 => ContentSource::Inline("413 Payload Too Large".to_owned()),
|
19
|
+
429 => ContentSource::Inline("429 Too Many Requests".to_owned()),
|
20
|
+
502 => ContentSource::Inline("502 Bad Gateway".to_owned()),
|
21
|
+
503 => ContentSource::Inline("503 Service Unavailable".to_owned()),
|
22
|
+
504 => ContentSource::Inline("504 Gateway Timeout".to_owned()),
|
23
|
+
_ => ContentSource::Inline("Unexpected Error".to_owned()),
|
24
|
+
},
|
25
|
+
DefaultFormat::Html => match code {
|
26
|
+
500 => ContentSource::Inline(
|
27
|
+
include_str!("../../../../default_responses/html/500.html").to_owned(),
|
28
|
+
),
|
29
|
+
404 => ContentSource::Inline(
|
30
|
+
include_str!("../../../../default_responses/html/404.html").to_owned(),
|
31
|
+
),
|
32
|
+
401 => ContentSource::Inline(
|
33
|
+
include_str!("../../../../default_responses/html/401.html").to_owned(),
|
34
|
+
),
|
35
|
+
403 => ContentSource::Inline(
|
36
|
+
include_str!("../../../../default_responses/html/403.html").to_owned(),
|
37
|
+
),
|
38
|
+
413 => ContentSource::Inline(
|
39
|
+
include_str!("../../../../default_responses/html/413.html").to_owned(),
|
40
|
+
),
|
41
|
+
429 => ContentSource::Inline(
|
42
|
+
include_str!("../../../../default_responses/html/429.html").to_owned(),
|
43
|
+
),
|
44
|
+
502 => ContentSource::Inline(
|
45
|
+
include_str!("../../../../default_responses/html/502.html").to_owned(),
|
46
|
+
),
|
47
|
+
503 => ContentSource::Inline(
|
48
|
+
include_str!("../../../../default_responses/html/503.html").to_owned(),
|
49
|
+
),
|
50
|
+
504 => ContentSource::Inline(
|
51
|
+
include_str!("../../../../default_responses/html/504.html").to_owned(),
|
52
|
+
),
|
53
|
+
_ => ContentSource::Inline("Unexpected Error".to_owned()),
|
54
|
+
},
|
55
|
+
DefaultFormat::Json => match code {
|
56
|
+
500 => ContentSource::Inline(
|
57
|
+
include_str!("../../../../default_responses/json/500.json").to_owned(),
|
58
|
+
),
|
59
|
+
404 => ContentSource::Inline(
|
60
|
+
include_str!("../../../../default_responses/json/404.json").to_owned(),
|
61
|
+
),
|
62
|
+
401 => ContentSource::Inline(
|
63
|
+
include_str!("../../../../default_responses/json/401.json").to_owned(),
|
64
|
+
),
|
65
|
+
403 => ContentSource::Inline(
|
66
|
+
include_str!("../../../../default_responses/json/403.json").to_owned(),
|
67
|
+
),
|
68
|
+
413 => ContentSource::Inline(
|
69
|
+
include_str!("../../../../default_responses/json/413.json").to_owned(),
|
70
|
+
),
|
71
|
+
429 => ContentSource::Inline(
|
72
|
+
include_str!("../../../../default_responses/json/429.json").to_owned(),
|
73
|
+
),
|
74
|
+
502 => ContentSource::Inline(
|
75
|
+
include_str!("../../../../default_responses/json/502.json").to_owned(),
|
76
|
+
),
|
77
|
+
503 => ContentSource::Inline(
|
78
|
+
include_str!("../../../../default_responses/json/503.json").to_owned(),
|
79
|
+
),
|
80
|
+
504 => ContentSource::Inline(
|
81
|
+
include_str!("../../../../default_responses/json/504.json").to_owned(),
|
82
|
+
),
|
83
|
+
_ => ContentSource::Inline("Unexpected Error".to_owned()),
|
84
|
+
},
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
impl ErrorResponse {
|
89
|
+
pub fn fallback_body_for(code: u16, accept: ResponseFormat) -> BoxBody<Bytes, Infallible> {
|
90
|
+
let source = match accept {
|
91
|
+
ResponseFormat::TEXT => DefaultFormat::Plaintext.response_for_code(code),
|
92
|
+
ResponseFormat::HTML => DefaultFormat::Html.response_for_code(code),
|
93
|
+
ResponseFormat::JSON => DefaultFormat::Json.response_for_code(code),
|
94
|
+
ResponseFormat::UNKNOWN => ContentSource::Inline("Unexpected Error".to_owned()),
|
95
|
+
};
|
96
|
+
match source {
|
97
|
+
ContentSource::Inline(bytes) => BoxBody::new(Full::new(Bytes::from(bytes))),
|
98
|
+
ContentSource::File(_) => BoxBody::new(Full::new(Bytes::from("Unexpected error"))),
|
99
|
+
}
|
100
|
+
}
|
101
|
+
pub fn internal_server_error() -> Self {
|
102
|
+
ErrorResponse {
|
103
|
+
code: 500,
|
104
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(500)),
|
105
|
+
html: Some(DefaultFormat::Html.response_for_code(500)),
|
106
|
+
json: Some(DefaultFormat::Json.response_for_code(500)),
|
107
|
+
default: DefaultFormat::Html,
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
pub fn not_found() -> Self {
|
112
|
+
ErrorResponse {
|
113
|
+
code: 404,
|
114
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(404)),
|
115
|
+
html: Some(DefaultFormat::Html.response_for_code(404)),
|
116
|
+
json: Some(DefaultFormat::Json.response_for_code(404)),
|
117
|
+
default: DefaultFormat::Html,
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
pub fn unauthorized() -> Self {
|
122
|
+
ErrorResponse {
|
123
|
+
code: 401,
|
124
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(401)),
|
125
|
+
html: Some(DefaultFormat::Html.response_for_code(401)),
|
126
|
+
json: Some(DefaultFormat::Json.response_for_code(401)),
|
127
|
+
default: DefaultFormat::Html,
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
pub fn forbidden() -> Self {
|
132
|
+
ErrorResponse {
|
133
|
+
code: 403,
|
134
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(403)),
|
135
|
+
html: Some(DefaultFormat::Html.response_for_code(403)),
|
136
|
+
json: Some(DefaultFormat::Json.response_for_code(403)),
|
137
|
+
default: DefaultFormat::Html,
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
pub fn payload_too_large() -> Self {
|
142
|
+
ErrorResponse {
|
143
|
+
code: 413,
|
144
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(413)),
|
145
|
+
html: Some(DefaultFormat::Html.response_for_code(413)),
|
146
|
+
json: Some(DefaultFormat::Json.response_for_code(413)),
|
147
|
+
default: DefaultFormat::Html,
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
pub fn too_many_requests() -> Self {
|
152
|
+
ErrorResponse {
|
153
|
+
code: 429,
|
154
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(429)),
|
155
|
+
html: Some(DefaultFormat::Html.response_for_code(429)),
|
156
|
+
json: Some(DefaultFormat::Json.response_for_code(429)),
|
157
|
+
default: DefaultFormat::Html,
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
pub fn bad_gateway() -> Self {
|
162
|
+
ErrorResponse {
|
163
|
+
code: 502,
|
164
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(502)),
|
165
|
+
html: Some(DefaultFormat::Html.response_for_code(502)),
|
166
|
+
json: Some(DefaultFormat::Json.response_for_code(502)),
|
167
|
+
default: DefaultFormat::Html,
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
pub fn service_unavailable() -> Self {
|
172
|
+
ErrorResponse {
|
173
|
+
code: 503,
|
174
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(503)),
|
175
|
+
html: Some(DefaultFormat::Html.response_for_code(503)),
|
176
|
+
json: Some(DefaultFormat::Json.response_for_code(503)),
|
177
|
+
default: DefaultFormat::Html,
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
pub fn gateway_timeout() -> Self {
|
182
|
+
ErrorResponse {
|
183
|
+
code: 504,
|
184
|
+
plaintext: Some(DefaultFormat::Plaintext.response_for_code(504)),
|
185
|
+
html: Some(DefaultFormat::Html.response_for_code(504)),
|
186
|
+
json: Some(DefaultFormat::Json.response_for_code(504)),
|
187
|
+
default: DefaultFormat::Html,
|
188
|
+
}
|
189
|
+
}
|
190
|
+
}
|