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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +126 -272
  3. data/Cargo.toml +6 -0
  4. data/crates/itsi_error/Cargo.toml +1 -0
  5. data/crates/itsi_error/src/lib.rs +100 -10
  6. data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
  7. data/crates/itsi_server/Cargo.toml +12 -11
  8. data/crates/itsi_server/src/default_responses/html/401.html +68 -0
  9. data/crates/itsi_server/src/default_responses/html/403.html +68 -0
  10. data/crates/itsi_server/src/default_responses/html/404.html +68 -0
  11. data/crates/itsi_server/src/default_responses/html/413.html +71 -0
  12. data/crates/itsi_server/src/default_responses/html/429.html +68 -0
  13. data/crates/itsi_server/src/default_responses/html/500.html +71 -0
  14. data/crates/itsi_server/src/default_responses/html/502.html +71 -0
  15. data/crates/itsi_server/src/default_responses/html/503.html +68 -0
  16. data/crates/itsi_server/src/default_responses/html/504.html +69 -0
  17. data/crates/itsi_server/src/default_responses/html/index.html +238 -0
  18. data/crates/itsi_server/src/default_responses/json/401.json +6 -0
  19. data/crates/itsi_server/src/default_responses/json/403.json +6 -0
  20. data/crates/itsi_server/src/default_responses/json/404.json +6 -0
  21. data/crates/itsi_server/src/default_responses/json/413.json +6 -0
  22. data/crates/itsi_server/src/default_responses/json/429.json +6 -0
  23. data/crates/itsi_server/src/default_responses/json/500.json +6 -0
  24. data/crates/itsi_server/src/default_responses/json/502.json +6 -0
  25. data/crates/itsi_server/src/default_responses/json/503.json +6 -0
  26. data/crates/itsi_server/src/default_responses/json/504.json +6 -0
  27. data/crates/itsi_server/src/default_responses/mod.rs +11 -0
  28. data/crates/itsi_server/src/lib.rs +58 -26
  29. data/crates/itsi_server/src/prelude.rs +2 -0
  30. data/crates/itsi_server/src/ruby_types/README.md +21 -0
  31. data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
  32. data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  33. data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
  34. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
  35. data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
  36. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
  37. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
  38. data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
  39. data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
  40. data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
  41. data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
  42. data/crates/itsi_server/src/server/binds/mod.rs +4 -0
  43. data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
  44. data/crates/itsi_server/src/server/http_message_types.rs +97 -0
  45. data/crates/itsi_server/src/server/io_stream.rs +2 -1
  46. data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +50 -29
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
  70. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  71. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
  72. data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
  73. data/crates/itsi_server/src/server/mod.rs +3 -9
  74. data/crates/itsi_server/src/server/process_worker.rs +21 -3
  75. data/crates/itsi_server/src/server/request_job.rs +2 -2
  76. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
  77. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
  78. data/crates/itsi_server/src/server/signal.rs +24 -41
  79. data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
  80. data/crates/itsi_server/src/server/thread_worker.rs +59 -28
  81. data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
  82. data/crates/itsi_server/src/services/mime_types.rs +1416 -0
  83. data/crates/itsi_server/src/services/mod.rs +6 -0
  84. data/crates/itsi_server/src/services/password_hasher.rs +83 -0
  85. data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
  86. data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
  87. data/crates/itsi_tracing/src/lib.rs +145 -55
  88. data/{Itsi.rb → foo/Itsi.rb} +6 -9
  89. data/gems/scheduler/Cargo.lock +7 -0
  90. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  91. data/gems/scheduler/test/helpers/test_helper.rb +0 -1
  92. data/gems/scheduler/test/test_address_resolve.rb +0 -1
  93. data/gems/scheduler/test/test_network_io.rb +1 -1
  94. data/gems/scheduler/test/test_process_wait.rb +0 -1
  95. data/gems/server/Cargo.lock +126 -272
  96. data/gems/server/exe/itsi +65 -19
  97. data/gems/server/itsi-server.gemspec +4 -3
  98. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
  99. data/gems/server/lib/itsi/http_request.rb +117 -17
  100. data/gems/server/lib/itsi/http_response.rb +2 -0
  101. data/gems/server/lib/itsi/passfile.rb +109 -0
  102. data/gems/server/lib/itsi/server/config/dsl.rb +171 -99
  103. data/gems/server/lib/itsi/server/config.rb +58 -23
  104. data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
  105. data/gems/server/lib/itsi/server/default_app/index.html +113 -89
  106. data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
  107. data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
  108. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
  109. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
  110. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  111. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  112. data/gems/server/lib/itsi/server/route_tester.rb +107 -0
  113. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
  114. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
  115. data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
  116. data/gems/server/lib/itsi/server/version.rb +1 -1
  117. data/gems/server/lib/itsi/server.rb +82 -12
  118. data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
  119. data/gems/server/lib/shell_completions/completions.rb +26 -0
  120. data/gems/server/test/helpers/test_helper.rb +2 -1
  121. data/lib/itsi/version.rb +1 -1
  122. data/sandbox/README.md +5 -0
  123. data/sandbox/itsi_file/Gemfile +4 -2
  124. data/sandbox/itsi_file/Gemfile.lock +48 -6
  125. data/sandbox/itsi_file/Itsi.rb +327 -129
  126. data/sandbox/itsi_file/call.json +1 -0
  127. data/sandbox/itsi_file/echo_client/Gemfile +10 -0
  128. data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
  129. data/sandbox/itsi_file/echo_client/README.md +95 -0
  130. data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
  131. data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
  132. data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
  133. data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
  134. data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
  135. data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
  136. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
  137. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
  138. data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
  139. data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
  140. data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
  141. data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
  142. data/sandbox/itsi_sandbox_async/config.ru +0 -1
  143. data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
  144. data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
  145. data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
  146. data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
  147. data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
  148. data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
  149. data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
  150. data/sandbox/itsi_sinatra/app.rb +0 -1
  151. data/sandbox/static_files/.env +1 -0
  152. data/sandbox/static_files/404.html +25 -0
  153. data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
  154. data/sandbox/static_files/about.html +68 -0
  155. data/sandbox/static_files/tiny.html +1 -0
  156. data/sandbox/static_files/writebook.zip +0 -0
  157. data/tasks.txt +28 -33
  158. metadata +87 -26
  159. data/crates/itsi_error/src/from.rs +0 -68
  160. data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
  161. data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
  162. data/crates/itsi_server/src/server/itsi_service.rs +0 -172
  163. data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
  164. data/crates/itsi_server/src/server/types.rs +0 -43
  165. data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
  166. data/sandbox/itsi_file/public/assets/index.html +0 -1
  167. /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
  168. /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
  169. /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 RequestContext) -> HttpResponse {
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
- use crate::server::{
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, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE},
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 RequestContext) -> HttpResponse {
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::server::{
3
- itsi_service::RequestContext,
4
- types::{HttpRequest, HttpResponse, RequestExt},
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::default)?);
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::default)?,
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::default)?,
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::default)?,
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::default)?,
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::default)?,
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::default)?,
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::default)?,
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::default)?);
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::default)?,
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::default)?,
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::default)?,
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::default)?,
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::default)?,
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 RequestContext,
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::default)?;
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(&self, mut resp: HttpResponse, context: &mut RequestContext) -> HttpResponse {
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::server::{
2
- itsi_service::RequestContext,
3
- types::{HttpRequest, HttpResponse},
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::default)?;
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::default(format!("Failed to set allowed IPs: {:?}", e)))?;
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 RequestContext,
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.to_http_response(&req).await,
46
+ self.error_response
47
+ .to_http_response(req.accept().into())
48
+ .await,
42
49
  ));
43
50
  }
44
51
  }
@@ -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
+ }