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,14 +1,14 @@
1
1
  use derive_more::Debug;
2
2
  use futures::StreamExt;
3
- use http::{request::Parts, Response, StatusCode, Version};
3
+ use http::{header::CONTENT_LENGTH, request::Parts, Response, StatusCode, Version};
4
4
  use http_body_util::{combinators::BoxBody, BodyExt, Empty};
5
- use itsi_error::from::CLIENT_CONNECTION_CLOSED;
5
+ use itsi_error::CLIENT_CONNECTION_CLOSED;
6
6
  use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
7
7
  use itsi_tracing::{debug, error};
8
8
  use magnus::{
9
9
  block::Proc,
10
10
  error::{ErrorType, Result as MagnusResult},
11
- Error,
11
+ Error, RHash, Symbol,
12
12
  };
13
13
  use magnus::{
14
14
  value::{LazyId, ReprValue},
@@ -21,11 +21,14 @@ use super::{
21
21
  itsi_body_proxy::{big_bytes::BigBytes, ItsiBody, ItsiBodyProxy},
22
22
  itsi_http_response::ItsiHttpResponse,
23
23
  };
24
- use crate::server::{
25
- byte_frame::ByteFrame,
26
- itsi_service::RequestContext,
27
- request_job::RequestJob,
28
- types::{HttpRequest, HttpResponse},
24
+ use crate::{
25
+ server::{
26
+ byte_frame::ByteFrame,
27
+ http_message_types::{HttpRequest, HttpResponse},
28
+ request_job::RequestJob,
29
+ size_limited_incoming::MaxBodySizeReached,
30
+ },
31
+ services::itsi_http_service::HttpRequestContext,
29
32
  };
30
33
 
31
34
  static ID_MESSAGE: LazyId = LazyId::new("message");
@@ -40,7 +43,8 @@ pub struct ItsiHttpRequest {
40
43
  pub response: ItsiHttpResponse,
41
44
  pub start: Instant,
42
45
  #[debug(skip)]
43
- pub context: RequestContext,
46
+ pub context: HttpRequestContext,
47
+ pub script_name: String,
44
48
  }
45
49
 
46
50
  impl fmt::Display for ItsiHttpRequest {
@@ -73,7 +77,7 @@ impl ItsiHttpRequest {
73
77
  }
74
78
  }
75
79
  }
76
- fn content_type_str(&self) -> &str {
80
+ pub fn content_type_str(&self) -> &str {
77
81
  self.parts
78
82
  .headers
79
83
  .get("Content-Type")
@@ -85,10 +89,49 @@ impl ItsiHttpRequest {
85
89
  self.content_type_str() == "application/json"
86
90
  }
87
91
 
92
+ pub fn url_params(&self) -> magnus::error::Result<RHash> {
93
+ let captures = self
94
+ .context
95
+ .matching_pattern
96
+ .as_ref()
97
+ .and_then(|re| re.captures(self.parts.uri.path()));
98
+ if let Some(caps) = &captures {
99
+ let re = self.context.matching_pattern.as_ref().unwrap();
100
+ let params = RHash::with_capacity(caps.len());
101
+ for (i, group_name) in re.capture_names().enumerate().skip(1) {
102
+ if let Some(name) = group_name {
103
+ if let Some(m) = caps.get(i) {
104
+ // Insert into the hash: key is the group name, value is the match.
105
+ params.aset(Symbol::new(name), m.as_str())?;
106
+ }
107
+ }
108
+ }
109
+ Ok(params)
110
+ } else {
111
+ Ok(RHash::new())
112
+ }
113
+ }
114
+
88
115
  pub fn is_html(&self) -> bool {
89
116
  self.content_type_str() == "text/html"
90
117
  }
91
118
 
119
+ pub fn is_url_encoded(&self) -> bool {
120
+ self.content_type_str() == "application/x-www-form-urlencoded"
121
+ }
122
+
123
+ pub fn is_multipart(&self) -> bool {
124
+ self.content_type_str().starts_with("multipart/form-data")
125
+ }
126
+
127
+ pub fn content_length(&self) -> Option<u64> {
128
+ self.parts
129
+ .headers
130
+ .get(CONTENT_LENGTH)
131
+ .and_then(|hv| hv.to_str().ok())
132
+ .and_then(|s| s.parse().ok())
133
+ }
134
+
92
135
  pub fn process(self, ruby: &Ruby, app_proc: Arc<HeapValue<Proc>>) -> magnus::error::Result<()> {
93
136
  let response = self.response.clone();
94
137
  let result = app_proc.call::<_, Value>((self,));
@@ -117,37 +160,48 @@ impl ItsiHttpRequest {
117
160
  pub(crate) async fn process_request(
118
161
  app: Arc<HeapValue<Proc>>,
119
162
  hyper_request: HttpRequest,
120
- context: &RequestContext,
163
+ context: &HttpRequestContext,
164
+ script_name: String,
165
+ nonblocking: bool,
121
166
  ) -> itsi_error::Result<HttpResponse> {
122
- let (request, mut receiver) = ItsiHttpRequest::new(hyper_request, context).await;
123
- let shutdown_channel = context.service.shutdown_channel.clone();
124
- let response = request.response.clone();
125
- match context
126
- .sender
127
- .send(RequestJob::ProcessHttpRequest(request, app))
128
- .await
129
- {
130
- Err(err) => {
131
- error!("Error occurred: {}", err);
132
- let mut response = Response::new(BoxBody::new(Empty::new()));
133
- *response.status_mut() = StatusCode::BAD_REQUEST;
134
- Ok(response)
167
+ match ItsiHttpRequest::new(hyper_request, context, script_name).await {
168
+ Ok((request, mut receiver)) => {
169
+ let shutdown_channel = context.service.shutdown_channel.clone();
170
+ let response = request.response.clone();
171
+ let sender = if nonblocking {
172
+ &context.nonblocking_sender
173
+ } else {
174
+ &context.sender
175
+ };
176
+ match sender
177
+ .send(RequestJob::ProcessHttpRequest(request, app))
178
+ .await
179
+ {
180
+ Err(err) => {
181
+ error!("Error occurred: {}", err);
182
+ let mut response = Response::new(BoxBody::new(Empty::new()));
183
+ *response.status_mut() = StatusCode::BAD_REQUEST;
184
+ Ok(response)
185
+ }
186
+ _ => match receiver.recv().await {
187
+ Some(first_frame) => Ok(response
188
+ .build(first_frame, receiver, shutdown_channel)
189
+ .await),
190
+ None => Ok(response
191
+ .build(ByteFrame::Empty, receiver, shutdown_channel)
192
+ .await),
193
+ },
194
+ }
135
195
  }
136
- _ => match receiver.recv().await {
137
- Some(first_frame) => Ok(response
138
- .build(first_frame, receiver, shutdown_channel)
139
- .await),
140
- None => Ok(response
141
- .build(ByteFrame::Empty, receiver, shutdown_channel)
142
- .await),
143
- },
196
+ Err(err_resp) => Ok(err_resp),
144
197
  }
145
198
  }
146
199
 
147
200
  pub(crate) async fn new(
148
201
  request: HttpRequest,
149
- context: &RequestContext,
150
- ) -> (ItsiHttpRequest, mpsc::Receiver<ByteFrame>) {
202
+ context: &HttpRequestContext,
203
+ script_name: String,
204
+ ) -> Result<(ItsiHttpRequest, mpsc::Receiver<ByteFrame>), HttpResponse> {
151
205
  let (parts, body) = request.into_parts();
152
206
  let body = if context.server_params.streamable_body {
153
207
  ItsiBody::Stream(ItsiBodyProxy::new(body))
@@ -155,23 +209,32 @@ impl ItsiHttpRequest {
155
209
  let mut body_bytes = BigBytes::new();
156
210
  let mut stream = body.into_data_stream();
157
211
  while let Some(chunk) = stream.next().await {
158
- let byte_array = chunk.unwrap().to_vec();
159
- body_bytes.write_all(&byte_array).unwrap();
212
+ match chunk {
213
+ Ok(byte_array) => body_bytes.write_all(&byte_array).unwrap(),
214
+ Err(e) => {
215
+ let mut err_resp = Response::new(BoxBody::new(Empty::new()));
216
+ if e.downcast_ref::<MaxBodySizeReached>().is_some() {
217
+ *err_resp.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
218
+ }
219
+ return Err(err_resp);
220
+ }
221
+ }
160
222
  }
161
223
  ItsiBody::Buffered(body_bytes)
162
224
  };
163
225
  let response_channel = mpsc::channel::<ByteFrame>(100);
164
- (
226
+ Ok((
165
227
  Self {
166
228
  context: context.clone(),
167
229
  version: parts.version,
168
230
  response: ItsiHttpResponse::new(parts.clone(), response_channel.0),
169
231
  start: Instant::now(),
232
+ script_name,
170
233
  body,
171
234
  parts,
172
235
  },
173
236
  response_channel.1,
174
- )
237
+ ))
175
238
  }
176
239
 
177
240
  pub(crate) fn path(&self) -> MagnusResult<&str> {
@@ -179,12 +242,12 @@ impl ItsiHttpRequest {
179
242
  .parts
180
243
  .uri
181
244
  .path()
182
- .strip_prefix(&self.context.server_params.script_name)
245
+ .strip_prefix(&self.script_name)
183
246
  .unwrap_or(self.parts.uri.path()))
184
247
  }
185
248
 
186
249
  pub(crate) fn script_name(&self) -> MagnusResult<&str> {
187
- Ok(&self.context.server_params.script_name)
250
+ Ok(&self.script_name)
188
251
  }
189
252
 
190
253
  pub(crate) fn query_string(&self) -> MagnusResult<&str> {
@@ -2,8 +2,9 @@ use bytes::{Bytes, BytesMut};
2
2
  use derive_more::Debug;
3
3
  use futures::stream::{unfold, StreamExt};
4
4
  use http::{
5
- header::TRANSFER_ENCODING, request::Parts, HeaderMap, HeaderName, HeaderValue, Request,
6
- Response, StatusCode,
5
+ header::{ACCEPT, TRANSFER_ENCODING},
6
+ request::Parts,
7
+ HeaderMap, HeaderName, HeaderValue, Request, Response, StatusCode,
7
8
  };
8
9
  use http_body_util::{combinators::BoxBody, Empty, Full, StreamBody};
9
10
  use hyper::{body::Frame, upgrade::Upgraded};
@@ -32,7 +33,8 @@ use tokio_util::io::ReaderStream;
32
33
  use tracing::warn;
33
34
 
34
35
  use crate::server::{
35
- byte_frame::ByteFrame, serve_strategy::single_mode::RunningPhase, types::HttpResponse,
36
+ byte_frame::ByteFrame, http_message_types::HttpResponse,
37
+ serve_strategy::single_mode::RunningPhase,
36
38
  };
37
39
 
38
40
  #[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)]
@@ -297,11 +299,12 @@ impl ItsiHttpResponse {
297
299
  self.data.response_writer.write().take();
298
300
  Ok(true)
299
301
  }
300
- fn accept_str(&self) -> &str {
302
+
303
+ pub fn accept_str(&self) -> &str {
301
304
  self.data
302
305
  .parts
303
306
  .headers
304
- .get("Content-Type")
307
+ .get(ACCEPT)
305
308
  .and_then(|hv| hv.to_str().ok()) // handle invalid utf-8
306
309
  .unwrap_or("application/x-www-form-urlencoded")
307
310
  }
@@ -71,7 +71,7 @@ const DEBOUNCE_DURATION: Duration = Duration::from_millis(500);
71
71
  pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<Option<OwnedFd>> {
72
72
  let (r_fd, w_fd): (OwnedFd, OwnedFd) = pipe().map_err(|e| {
73
73
  magnus::Error::new(
74
- magnus::exception::exception(),
74
+ magnus::exception::standard_error(),
75
75
  format!("Failed to create watcher pipe: {}", e),
76
76
  )
77
77
  })?;
@@ -79,7 +79,7 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
79
79
  let fork_result = unsafe {
80
80
  fork().map_err(|e| {
81
81
  magnus::Error::new(
82
- magnus::exception::exception(),
82
+ magnus::exception::standard_error(),
83
83
  format!("Failed to fork file watcher: {}", e),
84
84
  )
85
85
  })
@@ -107,13 +107,13 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
107
107
  let base_dir = extract_and_canonicalize_base_dir(&pattern);
108
108
  let glob = Glob::new(&pattern).map_err(|e| {
109
109
  magnus::Error::new(
110
- magnus::exception::exception(),
110
+ magnus::exception::standard_error(),
111
111
  format!("Failed to create watch glob: {}", e),
112
112
  )
113
113
  })?;
114
114
  let glob_set = GlobSetBuilder::new().add(glob).build().map_err(|e| {
115
115
  magnus::Error::new(
116
- magnus::exception::exception(),
116
+ magnus::exception::standard_error(),
117
117
  format!("Failed to create watch glob set: {}", e),
118
118
  )
119
119
  })?;
@@ -1,11 +1,14 @@
1
1
  use super::file_watcher::{self};
2
2
  use crate::{
3
3
  ruby_types::ITSI_SERVER_CONFIG,
4
- server::{bind::Bind, listener::Listener, middleware_stack::MiddlewareSet},
4
+ server::{
5
+ binds::{bind::Bind, listener::Listener},
6
+ middleware_stack::MiddlewareSet,
7
+ },
5
8
  };
6
9
  use derive_more::Debug;
7
- use itsi_rb_helpers::{call_with_gvl, print_rb_backtrace, HeapVal, HeapValue};
8
- use itsi_tracing::set_level;
10
+ use itsi_rb_helpers::{call_with_gvl, print_rb_backtrace, HeapValue};
11
+ use itsi_tracing::{set_format, set_level, set_target};
9
12
  use magnus::{
10
13
  block::Proc,
11
14
  error::Result,
@@ -22,8 +25,8 @@ use std::{
22
25
  os::fd::{AsRawFd, OwnedFd, RawFd},
23
26
  path::PathBuf,
24
27
  sync::{Arc, OnceLock},
28
+ time::Duration,
25
29
  };
26
-
27
30
  static DEFAULT_BIND: &str = "http://localhost:3000";
28
31
  static ID_BUILD_CONFIG: LazyId = LazyId::new("build_config");
29
32
  static ID_RELOAD_EXEC: LazyId = LazyId::new("reload_exec");
@@ -48,16 +51,17 @@ pub struct ServerParams {
48
51
  pub hooks: HashMap<String, HeapValue<Proc>>,
49
52
  pub preload: bool,
50
53
 
54
+ pub request_timeout: Option<Duration>,
51
55
  pub notify_watchers: Option<Vec<(String, Vec<Vec<String>>)>>,
52
56
  /// Worker params
53
57
  pub threads: u8,
54
- pub script_name: String,
58
+ pub scheduler_threads: Option<u8>,
55
59
  pub streamable_body: bool,
56
60
  pub multithreaded_reactor: bool,
61
+ pub pin_worker_cores: bool,
57
62
  pub scheduler_class: Option<String>,
58
63
  pub oob_gc_responses_threshold: Option<u64>,
59
64
  pub middleware_loader: HeapValue<Proc>,
60
- pub default_app_loader: HeapValue<Proc>,
61
65
  pub middleware: OnceLock<MiddlewareSet>,
62
66
  pub binds: Vec<Bind>,
63
67
  #[debug(skip)]
@@ -75,7 +79,6 @@ impl ServerParams {
75
79
  {
76
80
  ruby.require("itsi/scheduler")?;
77
81
  }
78
- let default_app: HeapVal = self.default_app_loader.call::<_, Value>(())?.into();
79
82
  let middleware = MiddlewareSet::new(
80
83
  self.middleware_loader
81
84
  .call::<_, Option<Value>>(())
@@ -85,7 +88,6 @@ impl ServerParams {
85
88
  }
86
89
  })?
87
90
  .map(|mw| mw.into()),
88
- default_app,
89
91
  )?;
90
92
  self.middleware.set(middleware).map_err(|_| {
91
93
  magnus::Error::new(
@@ -104,7 +106,12 @@ impl ServerParams {
104
106
  .unwrap_or(num_cpus::get() as u8);
105
107
  let worker_memory_limit: Option<u64> = rb_param_hash.fetch("worker_memory_limit")?;
106
108
  let silence: bool = rb_param_hash.fetch("silence")?;
107
- let multithreaded_reactor: bool = rb_param_hash.fetch("multithreaded_reactor")?;
109
+ let multithreaded_reactor: bool = rb_param_hash
110
+ .fetch::<_, Option<bool>>("multithreaded_reactor")?
111
+ .unwrap_or(workers == 1);
112
+ let pin_worker_cores: bool = rb_param_hash
113
+ .fetch::<_, Option<bool>>("pin_worker_cores")?
114
+ .unwrap_or(true);
108
115
  let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?;
109
116
 
110
117
  let hooks: Option<RHash> = rb_param_hash.fetch("hooks")?;
@@ -125,22 +132,34 @@ impl ServerParams {
125
132
  .transpose()?
126
133
  .unwrap_or_default();
127
134
  let preload: bool = rb_param_hash.fetch("preload")?;
135
+ let request_timeout: Option<u64> = rb_param_hash.fetch("request_timeout")?;
136
+ let request_timeout = request_timeout.map(Duration::from_secs);
137
+
128
138
  let notify_watchers: Option<Vec<(String, Vec<Vec<String>>)>> =
129
139
  rb_param_hash.fetch("notify_watchers")?;
130
140
  let threads: u8 = rb_param_hash.fetch("threads")?;
131
- let script_name: String = rb_param_hash.fetch("script_name")?;
141
+ let scheduler_threads: Option<u8> = rb_param_hash.fetch("scheduler_threads")?;
132
142
  let streamable_body: bool = rb_param_hash.fetch("streamable_body")?;
133
143
  let scheduler_class: Option<String> = rb_param_hash.fetch("scheduler_class")?;
134
144
  let oob_gc_responses_threshold: Option<u64> =
135
145
  rb_param_hash.fetch("oob_gc_responses_threshold")?;
136
146
  let middleware_loader: Proc = rb_param_hash.fetch("middleware_loader")?;
137
- let default_app_loader: Proc = rb_param_hash.fetch("default_app_loader")?;
138
147
  let log_level: Option<String> = rb_param_hash.fetch("log_level")?;
148
+ let log_target: Option<String> = rb_param_hash.fetch("log_target")?;
149
+ let log_format: Option<String> = rb_param_hash.fetch("log_format")?;
139
150
 
140
151
  if let Some(level) = log_level {
141
152
  set_level(&level);
142
153
  }
143
154
 
155
+ if let Some(target) = log_target {
156
+ set_target(&target);
157
+ }
158
+
159
+ if let Some(format) = log_format {
160
+ set_format(&format);
161
+ }
162
+
144
163
  let binds: Option<Vec<String>> = rb_param_hash.fetch("binds")?;
145
164
  let binds = binds
146
165
  .unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
@@ -154,7 +173,7 @@ impl ServerParams {
154
173
  let bind_to_fd_map: HashMap<String, i32> = serde_json::from_str(&preexisting_listeners)
155
174
  .map_err(|e| {
156
175
  magnus::Error::new(
157
- magnus::exception::exception(),
176
+ magnus::exception::standard_error(),
158
177
  format!("Invalid listener info: {}", e),
159
178
  )
160
179
  })?;
@@ -196,12 +215,14 @@ impl ServerParams {
196
215
  worker_memory_limit,
197
216
  silence,
198
217
  multithreaded_reactor,
218
+ pin_worker_cores,
199
219
  shutdown_timeout,
200
220
  hooks,
201
221
  preload,
222
+ request_timeout,
202
223
  notify_watchers,
203
224
  threads,
204
- script_name,
225
+ scheduler_threads,
205
226
  streamable_body,
206
227
  scheduler_class,
207
228
  oob_gc_responses_threshold,
@@ -209,7 +230,6 @@ impl ServerParams {
209
230
  listener_info: Mutex::new(listener_info),
210
231
  listeners: Mutex::new(listeners),
211
232
  middleware_loader: middleware_loader.into(),
212
- default_app_loader: default_app_loader.into(),
213
233
  middleware: OnceLock::new(),
214
234
  })
215
235
  }
@@ -310,13 +330,13 @@ impl ItsiServerConfig {
310
330
  .map(|(str, fd)| {
311
331
  let dupped_fd = dup(*fd).map_err(|errno| {
312
332
  magnus::Error::new(
313
- magnus::exception::exception(),
333
+ magnus::exception::standard_error(),
314
334
  format!("Errno {} while trying to dup {}", errno, fd),
315
335
  )
316
336
  })?;
317
337
  Self::clear_cloexec(dupped_fd).map_err(|e| {
318
338
  magnus::Error::new(
319
- magnus::exception::exception(),
339
+ magnus::exception::standard_error(),
320
340
  format!("Failed to clear cloexec flag for fd {}: {}", dupped_fd, e),
321
341
  )
322
342
  })?;
@@ -339,7 +359,7 @@ impl ItsiServerConfig {
339
359
  serde_json::to_string(&self.server_params.read().listener_info.lock().clone())
340
360
  .map_err(|e| {
341
361
  magnus::Error::new(
342
- magnus::exception::exception(),
362
+ magnus::exception::standard_error(),
343
363
  format!("Invalid listener info: {}", e),
344
364
  )
345
365
  })?;
@@ -1,6 +1,7 @@
1
1
  use crate::server::{
2
+ lifecycle_event::LifecycleEvent,
2
3
  serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode, ServeStrategy},
3
- signal::{clear_signal_handlers, reset_signal_handlers, send_shutdown_event},
4
+ signal::{clear_signal_handlers, reset_signal_handlers, send_lifecycle_event},
4
5
  };
5
6
  use itsi_rb_helpers::{call_without_gvl, print_rb_backtrace};
6
7
  use itsi_server_config::ItsiServerConfig;
@@ -35,7 +36,7 @@ impl ItsiServer {
35
36
  }
36
37
 
37
38
  pub fn stop(&self) -> Result<()> {
38
- send_shutdown_event();
39
+ send_lifecycle_event(LifecycleEvent::Shutdown);
39
40
  Ok(())
40
41
  }
41
42
 
@@ -44,7 +45,7 @@ impl ItsiServer {
44
45
  let result = if self.config.lock().server_params.read().silence {
45
46
  run_silently(|| self.build_and_run_strategy())
46
47
  } else {
47
- info!("Itsi - Rolling into action. 💨 ⚪ ");
48
+ info!("Itsi - Rolling into action. ⚪💨");
48
49
  self.build_and_run_strategy()
49
50
  };
50
51
  if let Err(e) = result {
@@ -1,9 +1,8 @@
1
1
  use magnus::{value::Lazy, Module, RClass, RModule};
2
2
 
3
3
  pub mod itsi_body_proxy;
4
- pub mod itsi_grpc_request;
5
- pub mod itsi_grpc_response;
6
- pub mod itsi_grpc_stream;
4
+ pub mod itsi_grpc_call;
5
+ pub mod itsi_grpc_response_stream;
7
6
  pub mod itsi_http_request;
8
7
  pub mod itsi_http_response;
9
8
  pub mod itsi_server;
@@ -36,20 +35,14 @@ pub static ITSI_BODY_PROXY: Lazy<RClass> = Lazy::new(|ruby| {
36
35
  .unwrap()
37
36
  });
38
37
 
39
- pub static ITSI_GRPC_REQUEST: Lazy<RClass> = Lazy::new(|ruby| {
38
+ pub static ITSI_GRPC_CALL: Lazy<RClass> = Lazy::new(|ruby| {
40
39
  ruby.get_inner(&ITSI_MODULE)
41
- .define_class("GrpcRequest", ruby.class_object())
40
+ .define_class("GrpcCall", ruby.class_object())
42
41
  .unwrap()
43
42
  });
44
43
 
45
- pub static ITSI_GRPC_STREAM: Lazy<RClass> = Lazy::new(|ruby| {
44
+ pub static ITSI_GRPC_RESPONSE_STREAM: Lazy<RClass> = Lazy::new(|ruby| {
46
45
  ruby.get_inner(&ITSI_MODULE)
47
- .define_class("GrpcStream", ruby.class_object())
48
- .unwrap()
49
- });
50
-
51
- pub static ITSI_GRPC_RESPONSE: Lazy<RClass> = Lazy::new(|ruby| {
52
- ruby.get_inner(&ITSI_MODULE)
53
- .define_class("GrpcResponse", ruby.class_object())
46
+ .define_class("GrpcResponseStream", ruby.class_object())
54
47
  .unwrap()
55
48
  });
@@ -2,6 +2,7 @@ use super::{
2
2
  bind_protocol::BindProtocol,
3
3
  tls::{configure_tls, ItsiTlsAcceptor},
4
4
  };
5
+ use crate::prelude::*;
5
6
  use itsi_error::ItsiError;
6
7
  use std::{
7
8
  collections::HashMap,
@@ -28,7 +29,19 @@ pub struct Bind {
28
29
  pub address: BindAddress,
29
30
  pub port: Option<u16>, // None for Unix Sockets
30
31
  pub protocol: BindProtocol,
31
- pub tls_config: Option<ItsiTlsAcceptor>,
32
+ pub tls_config: Option<TlsOptions>,
33
+ }
34
+
35
+ #[derive(Default, Clone)]
36
+ pub struct TlsOptions {
37
+ pub host: String,
38
+ pub options: HashMap<String, String>,
39
+ }
40
+
41
+ impl TlsOptions {
42
+ pub fn build_acceptor(&self) -> Result<ItsiTlsAcceptor> {
43
+ configure_tls(&self.host, &self.options)
44
+ }
32
45
  }
33
46
 
34
47
  impl Bind {
@@ -73,7 +86,7 @@ impl std::fmt::Debug for Bind {
73
86
  impl FromStr for Bind {
74
87
  type Err = ItsiError;
75
88
 
76
- fn from_str(s: &str) -> Result<Self, Self::Err> {
89
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
77
90
  let (protocol, remainder) = if let Some((proto, rest)) = s.split_once("://") {
78
91
  (proto.parse::<BindProtocol>()?, rest)
79
92
  } else {
@@ -138,9 +151,15 @@ impl FromStr for Bind {
138
151
 
139
152
  let tls_config = match protocol {
140
153
  BindProtocol::Http => None,
141
- BindProtocol::Https => Some(configure_tls(host, &options)?),
154
+ BindProtocol::Https => Some(TlsOptions {
155
+ host: host.to_owned(),
156
+ options,
157
+ }),
142
158
  BindProtocol::Unix => None,
143
- BindProtocol::Unixs => Some(configure_tls(host, &options)?),
159
+ BindProtocol::Unixs => Some(TlsOptions {
160
+ host: host.to_owned(),
161
+ options,
162
+ }),
144
163
  };
145
164
  let bind = Self {
146
165
  address,
@@ -1,7 +1,10 @@
1
+ use crate::prelude::*;
2
+ use crate::server::io_stream::IoStream;
3
+ use crate::server::serve_strategy::single_mode::RunningPhase;
4
+
1
5
  use super::bind::{Bind, BindAddress};
2
6
  use super::bind_protocol::BindProtocol;
3
- use super::io_stream::IoStream;
4
- use super::serve_strategy::single_mode::RunningPhase;
7
+
5
8
  use super::tls::ItsiTlsAcceptor;
6
9
  use itsi_error::{ItsiError, Result};
7
10
  use itsi_tracing::info;
@@ -17,7 +20,6 @@ use tokio::net::{unix, TcpStream, UnixStream};
17
20
  use tokio::sync::watch::Receiver;
18
21
  use tokio_rustls::TlsAcceptor;
19
22
  use tokio_stream::StreamExt;
20
- use tracing::error;
21
23
 
22
24
  pub(crate) enum Listener {
23
25
  Tcp(TcpListener),
@@ -143,7 +145,7 @@ impl TokioListener {
143
145
  ItsiTlsAcceptor::Automatic(acme_acceptor, _, rustls_config) => {
144
146
  let accept_future = acme_acceptor.accept(tcp_stream.0);
145
147
  match accept_future.await {
146
- Ok(None) => Err(ItsiError::Pass()),
148
+ Ok(None) => Err(ItsiError::Pass),
147
149
  Ok(Some(start_handshake)) => {
148
150
  let tls_stream = start_handshake.into_stream(rustls_config.clone()).await?;
149
151
  Ok(IoStream::TcpTls {
@@ -153,7 +155,7 @@ impl TokioListener {
153
155
  }
154
156
  Err(error) => {
155
157
  error!(error = format!("{:?}", error));
156
- Err(ItsiError::Pass())
158
+ Err(ItsiError::Pass)
157
159
  }
158
160
  }
159
161
  }
@@ -330,12 +332,18 @@ impl Listener {
330
332
  BindProtocol::Http => Listener::Tcp(revive_tcp_socket(fd)?),
331
333
  BindProtocol::Https => {
332
334
  let tcp_listener = revive_tcp_socket(fd)?;
333
- Listener::TcpTls((tcp_listener, bind.tls_config.unwrap()))
335
+ Listener::TcpTls((
336
+ tcp_listener,
337
+ bind.tls_config.unwrap().build_acceptor().unwrap(),
338
+ ))
334
339
  }
335
340
  _ => unreachable!(),
336
341
  },
337
342
  BindAddress::UnixSocket(_) => match bind.tls_config {
338
- Some(tls_config) => Listener::UnixTls((revive_unix_socket(fd)?, tls_config)),
343
+ Some(tls_config) => Listener::UnixTls((
344
+ revive_unix_socket(fd)?,
345
+ tls_config.build_acceptor().unwrap(),
346
+ )),
339
347
  None => Listener::Unix(revive_unix_socket(fd)?),
340
348
  },
341
349
  };
@@ -352,12 +360,18 @@ impl TryFrom<Bind> for Listener {
352
360
  BindProtocol::Http => Listener::Tcp(connect_tcp_socket(addr, bind.port.unwrap())?),
353
361
  BindProtocol::Https => {
354
362
  let tcp_listener = connect_tcp_socket(addr, bind.port.unwrap())?;
355
- Listener::TcpTls((tcp_listener, bind.tls_config.unwrap()))
363
+ Listener::TcpTls((
364
+ tcp_listener,
365
+ bind.tls_config.unwrap().build_acceptor().unwrap(),
366
+ ))
356
367
  }
357
368
  _ => unreachable!(),
358
369
  },
359
370
  BindAddress::UnixSocket(path) => match bind.tls_config {
360
- Some(tls_config) => Listener::UnixTls((connect_unix_socket(&path)?, tls_config)),
371
+ Some(tls_config) => Listener::UnixTls((
372
+ connect_unix_socket(&path)?,
373
+ tls_config.build_acceptor().unwrap(),
374
+ )),
361
375
  None => Listener::Unix(connect_unix_socket(&path)?),
362
376
  },
363
377
  };
@@ -393,8 +407,8 @@ fn connect_tcp_socket(addr: IpAddr, port: u16) -> Result<TcpListener> {
393
407
  };
394
408
  let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
395
409
  let socket_address: SocketAddr = SocketAddr::new(addr, port);
396
- socket.set_reuse_port(true).ok();
397
410
  socket.set_reuse_address(true).ok();
411
+ socket.set_reuse_port(true).ok();
398
412
  socket.set_nonblocking(true).ok();
399
413
  socket.set_nodelay(true).ok();
400
414
  socket.set_recv_buffer_size(262_144).ok();