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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +124 -109
  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 +8 -10
  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 +124 -109
  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 +116 -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 +160 -101
  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 +326 -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,19 +1,17 @@
1
1
  use super::MiddlewareLayer;
2
- use crate::ruby_types::itsi_grpc_request::ItsiGrpcRequest;
3
- use crate::server::static_file_server::ROOT_STATIC_FILE_SERVER;
4
- use crate::{
5
- ruby_types::itsi_http_request::ItsiHttpRequest,
6
- server::{
7
- itsi_service::RequestContext,
8
- types::{HttpRequest, HttpResponse},
9
- },
10
- };
2
+ use crate::ruby_types::itsi_grpc_call::ItsiGrpcCall;
3
+ use crate::ruby_types::itsi_http_request::ItsiHttpRequest;
4
+ use crate::server::http_message_types::{HttpRequest, HttpResponse};
5
+ use crate::services::itsi_http_service::HttpRequestContext;
6
+ use crate::services::static_file_server::ROOT_STATIC_FILE_SERVER;
11
7
  use async_trait::async_trait;
12
8
  use derive_more::Debug;
13
9
  use either::Either;
14
10
  use itsi_rb_helpers::{HeapVal, HeapValue};
15
11
  use magnus::{block::Proc, error::Result, value::ReprValue, Symbol};
12
+ use regex::Regex;
16
13
  use std::str::FromStr;
14
+ use std::sync::atomic::Ordering;
17
15
  use std::sync::Arc;
18
16
 
19
17
  #[derive(Debug)]
@@ -21,6 +19,8 @@ pub struct RubyApp {
21
19
  app: Arc<HeapValue<Proc>>,
22
20
  request_type: RequestType,
23
21
  sendfile: bool,
22
+ nonblocking: bool,
23
+ base_path: Regex,
24
24
  }
25
25
 
26
26
  #[derive(Debug)]
@@ -47,6 +47,14 @@ impl RubyApp {
47
47
  let sendfile = params
48
48
  .funcall::<_, _, bool>(Symbol::new("[]"), ("sendfile",))
49
49
  .unwrap_or(true);
50
+ let nonblocking = params
51
+ .funcall::<_, _, bool>(Symbol::new("[]"), ("nonblocking",))
52
+ .unwrap_or(false);
53
+ let base_path_src = params
54
+ .funcall::<_, _, String>(Symbol::new("[]"), ("base_path",))
55
+ .unwrap_or("".to_owned());
56
+ let base_path = Regex::new(&base_path_src).unwrap();
57
+
50
58
  let request_type: RequestType = params
51
59
  .funcall::<_, _, String>(Symbol::new("[]"), ("request_type",))
52
60
  .unwrap_or("http".to_string())
@@ -56,7 +64,9 @@ impl RubyApp {
56
64
  Ok(RubyApp {
57
65
  app: Arc::new(app.into()),
58
66
  sendfile,
67
+ nonblocking,
59
68
  request_type,
69
+ base_path,
60
70
  })
61
71
  }
62
72
  }
@@ -66,25 +76,48 @@ impl MiddlewareLayer for RubyApp {
66
76
  async fn before(
67
77
  &self,
68
78
  req: HttpRequest,
69
- context: &mut RequestContext,
79
+ context: &mut HttpRequestContext,
70
80
  ) -> Result<Either<HttpRequest, HttpResponse>> {
81
+ context.is_ruby_request.store(true, Ordering::SeqCst);
71
82
  match self.request_type {
72
- RequestType::Http => ItsiHttpRequest::process_request(self.app.clone(), req, context)
73
- .await
74
- .map_err(|e| e.into())
75
- .map(Either::Right),
76
- RequestType::Grpc => ItsiGrpcRequest::process_request(self.app.clone(), req, context)
83
+ RequestType::Http => {
84
+ let uri = req.uri().path();
85
+ let script_name = self
86
+ .base_path
87
+ .captures(uri)
88
+ .and_then(|caps| caps.name("base_path"))
89
+ .map(|m| m.as_str())
90
+ .unwrap_or("/")
91
+ .to_owned();
92
+ ItsiHttpRequest::process_request(
93
+ self.app.clone(),
94
+ req,
95
+ context,
96
+ script_name,
97
+ self.nonblocking,
98
+ )
77
99
  .await
78
100
  .map_err(|e| e.into())
79
- .map(Either::Right),
101
+ .map(Either::Right)
102
+ }
103
+ RequestType::Grpc => {
104
+ ItsiGrpcCall::process_request(self.app.clone(), req, context, self.nonblocking)
105
+ .await
106
+ .map_err(|e| e.into())
107
+ .map(Either::Right)
108
+ }
80
109
  }
81
110
  }
82
111
 
83
- async fn after(&self, resp: HttpResponse, _context: &mut RequestContext) -> HttpResponse {
112
+ async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
84
113
  if self.sendfile {
85
114
  if let Some(sendfile_header) = resp.headers().get("X-Sendfile") {
86
115
  return ROOT_STATIC_FILE_SERVER
87
- .serve_single(sendfile_header.to_str().unwrap())
116
+ .serve_single_abs(
117
+ sendfile_header.to_str().unwrap(),
118
+ context.accept.clone(),
119
+ &[],
120
+ )
88
121
  .await;
89
122
  }
90
123
  }
@@ -1,8 +1,12 @@
1
1
  use super::{FromValue, MiddlewareLayer};
2
- use crate::server::{
3
- itsi_service::RequestContext,
4
- static_file_server::{NotFoundBehavior, ServeRange, StaticFileServer, StaticFileServerConfig},
5
- types::{HttpRequest, HttpResponse},
2
+ use crate::{
3
+ server::http_message_types::{HttpRequest, HttpResponse},
4
+ services::{
5
+ itsi_http_service::HttpRequestContext,
6
+ static_file_server::{
7
+ NotFoundBehavior, ServeRange, StaticFileServer, StaticFileServerConfig,
8
+ },
9
+ },
6
10
  };
7
11
  use async_trait::async_trait;
8
12
  use either::Either;
@@ -12,6 +16,7 @@ use http::{
12
16
  };
13
17
  use itsi_error::ItsiError;
14
18
  use magnus::error::Result;
19
+ use regex::Regex;
15
20
  use serde::Deserialize;
16
21
  use std::{collections::HashMap, path::PathBuf, sync::OnceLock, time::Duration};
17
22
 
@@ -25,7 +30,12 @@ pub struct StaticAssets {
25
30
  pub max_files_in_memory: u64,
26
31
  pub file_check_interval: u64,
27
32
  pub headers: Option<HashMap<String, String>>,
33
+ pub allowed_extensions: Vec<String>,
28
34
  pub relative_path: bool,
35
+ pub serve_hidden_files: bool,
36
+ pub base_path: String,
37
+ #[serde(skip)]
38
+ pub base_path_regex: OnceLock<Regex>,
29
39
  #[serde(skip)]
30
40
  file_server: OnceLock<StaticFileServer>,
31
41
  }
@@ -46,6 +56,9 @@ impl MiddlewareLayer for StaticAssets {
46
56
  "Root directory exists but is not a directory".to_string(),
47
57
  ))
48
58
  }?;
59
+ self.base_path_regex
60
+ .set(Regex::new(&self.base_path).map_err(ItsiError::new)?)
61
+ .map_err(ItsiError::new)?;
49
62
  self.file_server
50
63
  .set(StaticFileServer::new(StaticFileServerConfig {
51
64
  root_dir: self.root_dir.clone(),
@@ -55,15 +68,17 @@ impl MiddlewareLayer for StaticAssets {
55
68
  try_html_extension: self.try_html_extension,
56
69
  max_file_size: self.max_file_size_in_memory,
57
70
  recheck_interval: Duration::from_secs(self.file_check_interval),
71
+ serve_hidden_files: self.serve_hidden_files,
72
+ allowed_extensions: self.allowed_extensions.clone(),
58
73
  }))
59
- .map_err(ItsiError::default)?;
74
+ .map_err(ItsiError::new)?;
60
75
  Ok(())
61
76
  }
62
77
 
63
78
  async fn before(
64
79
  &self,
65
80
  req: HttpRequest,
66
- context: &mut RequestContext,
81
+ context: &mut HttpRequestContext,
67
82
  ) -> Result<Either<HttpRequest, HttpResponse>> {
68
83
  // Only handle GET and HEAD requests
69
84
  if req.method() != Method::GET && req.method() != Method::HEAD {
@@ -73,13 +88,16 @@ impl MiddlewareLayer for StaticAssets {
73
88
  let rel_path = if !self.relative_path {
74
89
  abs_path
75
90
  } else {
76
- match context
77
- .matching_pattern
78
- .as_ref()
79
- .and_then(|pattern| pattern.captures(req.uri().path()))
80
- .and_then(|captures| captures.name("path_suffix"))
91
+ let base_path = self
92
+ .base_path_regex
93
+ .get()
94
+ .unwrap()
95
+ .captures(abs_path)
96
+ .and_then(|caps| caps.name("base_path"))
81
97
  .map(|m| m.as_str())
82
- {
98
+ .unwrap_or("/");
99
+
100
+ match abs_path.strip_prefix(base_path) {
83
101
  Some(suffix) => suffix,
84
102
  None => return Ok(Either::Left(req)),
85
103
  }
@@ -106,9 +124,9 @@ impl MiddlewareLayer for StaticAssets {
106
124
  serve_range,
107
125
  if_modified_since,
108
126
  is_head_request,
127
+ &context.supported_encoding_set,
109
128
  )
110
129
  .await;
111
-
112
130
  if response.is_none() {
113
131
  Ok(Either::Left(req))
114
132
  } else {
@@ -0,0 +1,55 @@
1
+ use std::sync::OnceLock;
2
+
3
+ use super::{FromValue, MiddlewareLayer};
4
+ use crate::server::http_message_types::{HttpRequest, HttpResponse};
5
+ use crate::services::itsi_http_service::HttpRequestContext;
6
+ use async_trait::async_trait;
7
+ use bytes::Bytes;
8
+ use derive_more::Debug;
9
+ use either::Either;
10
+ use http::{HeaderMap, HeaderName, HeaderValue, Response, StatusCode};
11
+ use http_body_util::combinators::BoxBody;
12
+ use http_body_util::Full;
13
+ use itsi_error::ItsiError;
14
+ use magnus::error::Result;
15
+ use serde::Deserialize;
16
+
17
+ #[derive(Debug, Deserialize)]
18
+ pub struct StaticResponse {
19
+ code: u16,
20
+ headers: Vec<(String, String)>,
21
+ body: Vec<u8>,
22
+ #[serde(skip)]
23
+ header_map: OnceLock<HeaderMap>,
24
+ }
25
+
26
+ #[async_trait]
27
+ impl MiddlewareLayer for StaticResponse {
28
+ async fn initialize(&self) -> Result<()> {
29
+ let mut header_map = HeaderMap::new();
30
+ for (key, value) in self.headers.iter() {
31
+ if let (Ok(hn), Ok(hv)) = (key.parse::<HeaderName>(), value.parse::<HeaderValue>()) {
32
+ header_map.insert(hn, hv);
33
+ }
34
+ }
35
+ self.header_map
36
+ .set(header_map)
37
+ .map_err(|_| ItsiError::new("Failed to set headers"))?;
38
+ Ok(())
39
+ }
40
+
41
+ async fn before(
42
+ &self,
43
+ _req: HttpRequest,
44
+ _context: &mut HttpRequestContext,
45
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
46
+ let mut resp = Response::new(BoxBody::new(Full::new(Bytes::from(self.body.clone()))));
47
+ let status = StatusCode::from_u16(self.code).unwrap_or(StatusCode::OK);
48
+ *resp.status_mut() = status;
49
+ *resp.headers_mut() = self.header_map.get().unwrap().clone();
50
+
51
+ Ok(Either::Right(resp))
52
+ }
53
+ }
54
+
55
+ impl FromValue for StaticResponse {}
@@ -1,10 +1,11 @@
1
- use crate::server::{
2
- itsi_service::RequestContext,
3
- types::{HttpRequest, HttpResponse},
4
- };
5
1
  use serde::Deserialize;
6
2
  use std::sync::OnceLock;
7
3
 
4
+ use crate::{
5
+ server::http_message_types::{HttpRequest, HttpResponse},
6
+ services::itsi_http_service::HttpRequestContext,
7
+ };
8
+
8
9
  #[derive(Debug, Clone, Deserialize)]
9
10
  #[serde(transparent)]
10
11
  pub struct StringRewrite {
@@ -49,7 +50,7 @@ pub fn parse_template(template: &str) -> Vec<Segment> {
49
50
  }
50
51
 
51
52
  impl StringRewrite {
52
- pub fn rewrite_request(&self, req: &HttpRequest, context: &RequestContext) -> String {
53
+ pub fn rewrite_request(&self, req: &HttpRequest, context: &HttpRequestContext) -> String {
53
54
  let segments = self
54
55
  .segments
55
56
  .get_or_init(|| parse_template(&self.template_string));
@@ -65,9 +66,11 @@ impl StringRewrite {
65
66
  Segment::Literal(text) => result.push_str(text),
66
67
  Segment::Placeholder(placeholder) => {
67
68
  let replacement = match placeholder.as_str() {
68
- "request_id" => context.request_id(),
69
+ "request_id" => context.short_request_id(),
70
+ "request_id_full" => context.request_id(),
69
71
  "method" => req.method().as_str().to_string(),
70
72
  "path" => req.uri().path().to_string(),
73
+ "addr" => context.addr.to_owned(),
71
74
  "host" => req.uri().host().unwrap_or("localhost").to_string(),
72
75
  "path_and_query" => req
73
76
  .uri()
@@ -116,7 +119,7 @@ impl StringRewrite {
116
119
  result
117
120
  }
118
121
 
119
- pub fn rewrite_response(&self, resp: &HttpResponse, context: &RequestContext) -> String {
122
+ pub fn rewrite_response(&self, resp: &HttpResponse, context: &HttpRequestContext) -> String {
120
123
  let segments = self
121
124
  .segments
122
125
  .get_or_init(|| parse_template(&self.template_string));
@@ -127,8 +130,10 @@ impl StringRewrite {
127
130
  Segment::Literal(text) => result.push_str(text),
128
131
  Segment::Placeholder(placeholder) => {
129
132
  let replacement = match placeholder.as_str() {
130
- "request_id" => context.request_id(),
133
+ "request_id" => context.short_request_id(),
134
+ "request_id_full" => context.request_id(),
131
135
  "status" => resp.status().as_str().to_string(),
136
+ "addr" => context.addr.to_owned(),
132
137
  "response_time" => {
133
138
  if let Some(response_time) = context.get_response_time() {
134
139
  if let Some(microseconds) = response_time.num_microseconds() {
@@ -1,6 +1,5 @@
1
1
  mod middleware;
2
2
  mod middlewares;
3
- use super::types::HttpRequest;
4
3
  use http::header::{ACCEPT, CONTENT_TYPE, HOST};
5
4
  use itsi_rb_helpers::HeapVal;
6
5
  use magnus::{error::Result, value::ReprValue, RArray, RHash, Ruby, TryConvert, Value};
@@ -8,14 +7,15 @@ pub use middleware::Middleware;
8
7
  pub use middlewares::*;
9
8
  use regex::{Regex, RegexSet};
10
9
  use std::{collections::HashMap, sync::Arc};
11
- use tracing::info;
10
+ use tracing::debug;
11
+
12
+ use super::http_message_types::HttpRequest;
12
13
 
13
14
  #[derive(Debug)]
14
15
  pub struct MiddlewareSet {
15
16
  pub route_set: RegexSet,
16
17
  pub patterns: Vec<Arc<Regex>>,
17
18
  pub stacks: HashMap<usize, MiddlewareStack>,
18
- pub default_stack: Vec<Middleware>,
19
19
  }
20
20
 
21
21
  #[derive(Debug)]
@@ -43,7 +43,7 @@ impl StringMatch {
43
43
  let src_str = value.funcall::<_, _, String>("source", ())?;
44
44
  let regex = Regex::new(&src_str).map_err(|e| {
45
45
  magnus::Error::new(
46
- magnus::exception::exception(),
46
+ magnus::exception::standard_error(),
47
47
  format!("Invalid regexp: {}", e),
48
48
  )
49
49
  })?;
@@ -86,13 +86,15 @@ impl MiddlewareStack {
86
86
 
87
87
  if let (Some(ports), Some(port)) = (&self.ports, request.uri().port()) {
88
88
  if !ports.iter().any(|d| d.matches(port.as_str())) {
89
- info!("No match between port {} and {:?}", port, ports);
90
89
  return false;
91
90
  }
92
91
  }
93
92
 
94
93
  if let Some(extensions) = &self.extensions {
95
- let extension = request.uri().path().split('.').next_back().unwrap_or("");
94
+ let path = request.uri().path();
95
+ let segment = path.split('/').next_back().unwrap_or("");
96
+ let extension = segment.split('.').next_back().unwrap_or("");
97
+ let extension = if segment != extension { extension } else { "" };
96
98
  if !extensions.iter().any(|e| e.matches(extension)) {
97
99
  return false;
98
100
  }
@@ -125,13 +127,13 @@ impl MiddlewareStack {
125
127
  }
126
128
 
127
129
  impl MiddlewareSet {
128
- pub fn new(routes_raw: Option<HeapVal>, default_app: HeapVal) -> Result<Self> {
130
+ pub fn new(routes_raw: Option<HeapVal>) -> Result<Self> {
129
131
  if let Some(routes_raw) = routes_raw {
130
132
  let mut stacks = HashMap::new();
131
133
  let mut routes = vec![];
132
134
  for (index, route) in RArray::from_value(*routes_raw)
133
135
  .ok_or(magnus::Error::new(
134
- magnus::exception::exception(),
136
+ magnus::exception::standard_error(),
135
137
  format!("Routes must be an array. Got {:?}", routes_raw),
136
138
  ))?
137
139
  .into_iter()
@@ -141,17 +143,17 @@ impl MiddlewareSet {
141
143
  let route_raw = route_hash
142
144
  .get("route")
143
145
  .ok_or(magnus::Error::new(
144
- magnus::exception::exception(),
146
+ magnus::exception::standard_error(),
145
147
  "Route is missing :route key",
146
148
  ))?
147
149
  .funcall::<_, _, String>("source", ())?;
148
150
  let middleware =
149
151
  RArray::from_value(route_hash.get("middleware").ok_or(magnus::Error::new(
150
- magnus::exception::exception(),
152
+ magnus::exception::standard_error(),
151
153
  "Route is missing middleware key",
152
154
  ))?)
153
155
  .ok_or(magnus::Error::new(
154
- magnus::exception::exception(),
156
+ magnus::exception::standard_error(),
155
157
  format!("middleware must be an array. Got {:?}", routes_raw),
156
158
  ))?;
157
159
 
@@ -159,9 +161,6 @@ impl MiddlewareSet {
159
161
  .into_iter()
160
162
  .map(MiddlewareSet::parse_middleware)
161
163
  .collect::<Result<Vec<_>>>()?;
162
- layers.push(Middleware::RubyApp(RubyApp::from_value(
163
- default_app.clone(),
164
- )?));
165
164
  routes.push(route_raw);
166
165
  layers.sort();
167
166
  stacks.insert(
@@ -178,11 +177,10 @@ impl MiddlewareSet {
178
177
  },
179
178
  );
180
179
  }
181
- info!("Routes are {:?}", routes);
182
180
  Ok(Self {
183
181
  route_set: RegexSet::new(&routes).map_err(|e| {
184
182
  magnus::Error::new(
185
- magnus::exception::exception(),
183
+ magnus::exception::standard_error(),
186
184
  format!("Failed to create route set: {}", e),
187
185
  )
188
186
  })?,
@@ -192,7 +190,7 @@ impl MiddlewareSet {
192
190
  .collect::<std::result::Result<Vec<Regex>, regex::Error>>()
193
191
  .map_err(|e| {
194
192
  magnus::Error::new(
195
- magnus::exception::exception(),
193
+ magnus::exception::standard_error(),
196
194
  format!("Failed to create route set: {}", e),
197
195
  )
198
196
  })?
@@ -200,97 +198,131 @@ impl MiddlewareSet {
200
198
  .map(Arc::new)
201
199
  .collect(),
202
200
  stacks,
203
- default_stack: vec![Middleware::RubyApp(RubyApp::from_value(default_app)?)],
204
201
  })
205
202
  } else {
206
- Ok(Self {
207
- route_set: RegexSet::empty(),
208
- patterns: Vec::new(),
209
- stacks: HashMap::new(),
210
- default_stack: vec![Middleware::RubyApp(RubyApp::from_value(default_app)?)],
211
- })
203
+ Err(magnus::Error::new(
204
+ magnus::exception::standard_error(),
205
+ "Failed to create middleware stack",
206
+ ))
212
207
  }
213
208
  }
214
209
 
215
- pub fn stack_for(&self, request: &HttpRequest) -> (&Vec<Middleware>, Option<Arc<Regex>>) {
210
+ pub fn stack_for(
211
+ &self,
212
+ request: &HttpRequest,
213
+ ) -> Result<(&Vec<Middleware>, Option<Arc<Regex>>)> {
216
214
  let binding = self.route_set.matches(request.uri().path());
217
215
  let matches = binding.iter();
216
+
217
+ debug!(
218
+ "Matching request URI {:?} against self.route_set: {:?}",
219
+ request.uri().path(),
220
+ self.route_set
221
+ );
218
222
  for index in matches {
219
223
  let matching_pattern = self.patterns.get(index).cloned();
220
224
  if let Some(stack) = self.stacks.get(&index) {
221
225
  if stack.matches(request) {
222
- return (&stack.layers, matching_pattern);
226
+ return Ok((&stack.layers, matching_pattern));
223
227
  }
224
228
  }
225
229
  }
226
- (self.default_stack(), None)
230
+ debug!(
231
+ "Failed to match request URI {:?} to self.route_set: {:?}",
232
+ request.uri().path(),
233
+ self.route_set
234
+ );
235
+ Err(magnus::Error::new(
236
+ magnus::exception::standard_error(),
237
+ format!(
238
+ "No matching middleware stack found for request: {:?}",
239
+ request
240
+ ),
241
+ ))
227
242
  }
228
243
 
229
244
  pub fn parse_middleware(middleware: Value) -> Result<Middleware> {
230
245
  let middleware_hash = RHash::from_value(middleware).ok_or(magnus::Error::new(
231
- magnus::exception::exception(),
246
+ magnus::exception::standard_error(),
232
247
  format!("Filter must be a hash. Got {:?}", middleware),
233
248
  ))?;
234
249
  let middleware_type: String = middleware_hash
235
250
  .get("type")
236
251
  .ok_or(magnus::Error::new(
237
- magnus::exception::exception(),
252
+ magnus::exception::standard_error(),
238
253
  format!("Filter must have a :type key. Got {:?}", middleware_hash),
239
254
  ))?
240
255
  .to_string();
256
+ let mw_type = middleware_type.clone();
241
257
 
242
258
  let parameters: Value = middleware_hash.get("parameters").ok_or(magnus::Error::new(
243
- magnus::exception::exception(),
259
+ magnus::exception::standard_error(),
244
260
  format!(
245
261
  "Filter must have a :parameters key. Got {:?}",
246
262
  middleware_hash
247
263
  ),
248
264
  ))?;
249
265
 
250
- let result = match middleware_type.as_str() {
251
- "allow_list" => Middleware::AllowList(AllowList::from_value(parameters)?),
252
- "auth_basic" => Middleware::AuthBasic(AuthBasic::from_value(parameters)?),
253
- "auth_jwt" => Middleware::AuthJwt(Box::new(AuthJwt::from_value(parameters)?)),
254
- "auth_api_key" => Middleware::AuthAPIKey(AuthAPIKey::from_value(parameters)?),
255
- "cache_control" => Middleware::CacheControl(CacheControl::from_value(parameters)?),
256
- "deny_list" => Middleware::DenyList(DenyList::from_value(parameters)?),
257
- "etag" => Middleware::ETag(ETag::from_value(parameters)?),
258
- "intrusion_protection" => {
259
- Middleware::IntrusionProtection(IntrusionProtection::from_value(parameters)?)
266
+ let result = (move || -> Result<Middleware> {
267
+ match mw_type.as_str() {
268
+ "allow_list" => Ok(Middleware::AllowList(AllowList::from_value(parameters)?)),
269
+ "auth_basic" => Ok(Middleware::AuthBasic(AuthBasic::from_value(parameters)?)),
270
+ "auth_jwt" => Ok(Middleware::AuthJwt(Box::new(AuthJwt::from_value(
271
+ parameters,
272
+ )?))),
273
+ "auth_api_key" => Ok(Middleware::AuthAPIKey(AuthAPIKey::from_value(parameters)?)),
274
+ "cache_control" => Ok(Middleware::CacheControl(CacheControl::from_value(
275
+ parameters,
276
+ )?)),
277
+ "deny_list" => Ok(Middleware::DenyList(DenyList::from_value(parameters)?)),
278
+ "etag" => Ok(Middleware::ETag(ETag::from_value(parameters)?)),
279
+ "intrusion_protection" => Ok({
280
+ Middleware::IntrusionProtection(IntrusionProtection::from_value(parameters)?)
281
+ }),
282
+ "max_body" => Ok(Middleware::MaxBody(MaxBody::from_value(parameters)?)),
283
+ "rate_limit" => Ok(Middleware::RateLimit(RateLimit::from_value(parameters)?)),
284
+ "cors" => Ok(Middleware::Cors(Box::new(Cors::from_value(parameters)?))),
285
+ "request_headers" => Ok(Middleware::RequestHeaders(RequestHeaders::from_value(
286
+ parameters,
287
+ )?)),
288
+ "response_headers" => Ok(Middleware::ResponseHeaders(ResponseHeaders::from_value(
289
+ parameters,
290
+ )?)),
291
+ "static_assets" => Ok(Middleware::StaticAssets(StaticAssets::from_value(
292
+ parameters,
293
+ )?)),
294
+ "static_response" => Ok(Middleware::StaticResponse(StaticResponse::from_value(
295
+ parameters,
296
+ )?)),
297
+ "compression" => Ok(Middleware::Compression(Compression::from_value(
298
+ parameters,
299
+ )?)),
300
+ "log_requests" => Ok(Middleware::LogRequests(LogRequests::from_value(
301
+ parameters,
302
+ )?)),
303
+ "redirect" => Ok(Middleware::Redirect(Redirect::from_value(parameters)?)),
304
+ "app" => Ok(Middleware::RubyApp(RubyApp::from_value(parameters.into())?)),
305
+ "proxy" => Ok(Middleware::Proxy(Proxy::from_value(parameters)?)),
306
+ _ => Err(magnus::Error::new(
307
+ magnus::exception::standard_error(),
308
+ format!("Unknown filter type: {}", mw_type),
309
+ )),
260
310
  }
261
- "rate_limit" => Middleware::RateLimit(RateLimit::from_value(parameters)?),
262
- "cors" => Middleware::Cors(Box::new(Cors::from_value(parameters)?)),
263
- "request_headers" => {
264
- Middleware::RequestHeaders(RequestHeaders::from_value(parameters)?)
265
- }
266
- "response_headers" => {
267
- Middleware::ResponseHeaders(ResponseHeaders::from_value(parameters)?)
268
- }
269
- "static_assets" => Middleware::StaticAssets(StaticAssets::from_value(parameters)?),
270
- "compression" => Middleware::Compression(Compression::from_value(parameters)?),
271
- "log_requests" => Middleware::LogRequests(LogRequests::from_value(parameters)?),
272
- "redirect" => Middleware::Redirect(Redirect::from_value(parameters)?),
273
- "app" => Middleware::RubyApp(RubyApp::from_value(parameters.into())?),
274
- "proxy" => Middleware::Proxy(Proxy::from_value(parameters)?),
275
- _ => {
276
- return Err(magnus::Error::new(
277
- magnus::exception::exception(),
278
- format!("Unknown filter type: {}", middleware_type),
279
- ))
280
- }
281
- };
311
+ })();
282
312
 
283
- Ok(result)
284
- }
285
-
286
- fn default_stack(&self) -> &Vec<Middleware> {
287
- &self.default_stack
313
+ match result {
314
+ Ok(result) => Ok(result),
315
+ Err(err) => Err(magnus::Error::new(
316
+ magnus::exception::standard_error(),
317
+ format!(
318
+ "Failed to instantiate middleware of type {}, due to {}",
319
+ middleware_type, err
320
+ ),
321
+ )),
322
+ }
288
323
  }
289
324
 
290
325
  pub async fn initialize_layers(&self) -> Result<()> {
291
- for middleware in &self.default_stack {
292
- middleware.initialize().await?;
293
- }
294
326
  for stack in self.stacks.values() {
295
327
  for middleware in &stack.layers {
296
328
  middleware.initialize().await?;
@@ -1,18 +1,12 @@
1
- pub mod bind;
2
- pub mod bind_protocol;
1
+ pub mod binds;
3
2
  pub mod byte_frame;
4
- pub mod cache_store;
3
+ pub mod http_message_types;
5
4
  pub mod io_stream;
6
- pub mod itsi_service;
7
5
  pub mod lifecycle_event;
8
- pub mod listener;
9
6
  pub mod middleware_stack;
10
7
  pub mod process_worker;
11
- pub mod rate_limiter;
12
8
  pub mod request_job;
13
9
  pub mod serve_strategy;
14
10
  pub mod signal;
15
- pub mod static_file_server;
11
+ pub mod size_limited_incoming;
16
12
  pub mod thread_worker;
17
- pub mod tls;
18
- pub mod types;