itsi-server 0.1.1 → 0.1.9

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.

Potentially problematic release.


This version of itsi-server might be problematic. Click here for more details.

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +2917 -0
  3. data/Cargo.toml +7 -0
  4. data/Rakefile +8 -1
  5. data/exe/itsi +88 -28
  6. data/ext/itsi_error/Cargo.toml +2 -0
  7. data/ext/itsi_error/src/from.rs +68 -0
  8. data/ext/itsi_error/src/lib.rs +13 -38
  9. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  10. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  11. data/ext/itsi_rb_helpers/Cargo.toml +2 -0
  12. data/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
  13. data/ext/itsi_rb_helpers/src/lib.rs +90 -10
  14. data/ext/itsi_scheduler/Cargo.toml +24 -0
  15. data/ext/itsi_scheduler/extconf.rb +6 -0
  16. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  17. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  18. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  19. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  20. data/ext/itsi_scheduler/src/lib.rs +38 -0
  21. data/ext/itsi_server/Cargo.lock +2956 -0
  22. data/ext/itsi_server/Cargo.toml +21 -3
  23. data/ext/itsi_server/extconf.rb +1 -1
  24. data/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  25. data/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  26. data/ext/itsi_server/src/body_proxy/mod.rs +2 -0
  27. data/ext/itsi_server/src/env.rs +43 -0
  28. data/ext/itsi_server/src/lib.rs +67 -7
  29. data/ext/itsi_server/src/request/itsi_request.rs +265 -103
  30. data/ext/itsi_server/src/response/itsi_response.rs +357 -0
  31. data/ext/itsi_server/src/response/mod.rs +1 -0
  32. data/ext/itsi_server/src/server/bind.rs +57 -25
  33. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  34. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  35. data/ext/itsi_server/src/server/itsi_server.rs +251 -139
  36. data/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
  37. data/ext/itsi_server/src/server/listener.rs +237 -137
  38. data/ext/itsi_server/src/server/mod.rs +7 -1
  39. data/ext/itsi_server/src/server/process_worker.rs +196 -0
  40. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
  41. data/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  42. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +263 -0
  43. data/ext/itsi_server/src/server/signal.rs +77 -0
  44. data/ext/itsi_server/src/server/thread_worker.rs +367 -0
  45. data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
  46. data/ext/itsi_server/src/server/tls.rs +187 -60
  47. data/ext/itsi_tracing/Cargo.toml +4 -0
  48. data/ext/itsi_tracing/src/lib.rs +53 -6
  49. data/lib/itsi/index.html +91 -0
  50. data/lib/itsi/request.rb +38 -14
  51. data/lib/itsi/server/rack/handler/itsi.rb +24 -0
  52. data/lib/itsi/server/rack_interface.rb +79 -0
  53. data/lib/itsi/server/scheduler_interface.rb +21 -0
  54. data/lib/itsi/server/scheduler_mode.rb +6 -0
  55. data/lib/itsi/server/signal_trap.rb +24 -0
  56. data/lib/itsi/server/version.rb +1 -1
  57. data/lib/itsi/server.rb +75 -9
  58. data/lib/itsi/stream_io.rb +38 -0
  59. metadata +46 -27
  60. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  61. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  62. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  63. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
@@ -1,143 +1,305 @@
1
- use std::{collections::HashMap, sync::Arc};
2
-
3
- use crate::server::listener::{Listener, SockAddr};
1
+ use crate::{
2
+ body_proxy::{
3
+ big_bytes::BigBytes,
4
+ itsi_body_proxy::{ItsiBody, ItsiBodyProxy},
5
+ },
6
+ response::itsi_response::ItsiResponse,
7
+ server::{
8
+ itsi_server::{RequestJob, Server},
9
+ listener::{ListenerInfo, SockAddr},
10
+ serve_strategy::single_mode::RunningPhase,
11
+ },
12
+ };
4
13
  use bytes::Bytes;
5
- use http::request::Parts;
6
- use http_body_util::BodyExt;
14
+ use derive_more::Debug;
15
+ use futures::StreamExt;
16
+ use http::{request::Parts, HeaderValue, Response, StatusCode};
17
+ use http_body_util::{combinators::BoxBody, BodyExt, Empty};
7
18
  use hyper::{body::Incoming, Request};
8
- use magnus::error::Result;
19
+ use itsi_error::from::CLIENT_CONNECTION_CLOSED;
20
+ use itsi_tracing::{debug, error};
21
+ use magnus::{
22
+ error::{ErrorType, Result as MagnusResult},
23
+ Error,
24
+ };
25
+ use magnus::{
26
+ value::{LazyId, Opaque, ReprValue},
27
+ RClass, Ruby, Value,
28
+ };
29
+ use std::{convert::Infallible, fmt, io::Write, sync::Arc, time::Instant};
30
+ use tokio::sync::{
31
+ mpsc::{self},
32
+ watch,
33
+ };
34
+ static ID_CALL: LazyId = LazyId::new("call");
35
+ static ID_MESSAGE: LazyId = LazyId::new("message");
36
+ static ID_BACKTRACE: LazyId = LazyId::new("backtrace");
9
37
 
10
- #[magnus::wrap(class = "Itsi::Request", free_immediately, size)]
11
38
  #[derive(Debug)]
39
+ #[magnus::wrap(class = "Itsi::Request", free_immediately, size)]
12
40
  pub struct ItsiRequest {
13
- pub path: String,
14
- pub script_name: String,
15
- pub query_string: String,
16
- pub method: String,
17
- pub version: String,
18
- pub rack_protocol: Vec<String>,
19
- pub host: String,
20
- pub scheme: String,
21
- pub headers: HashMap<String, String>,
22
- pub remote_addr: String,
23
- pub port: u16,
24
- pub body: Bytes,
25
41
  pub parts: Parts,
42
+ #[debug(skip)]
43
+ pub body: ItsiBody,
44
+ pub remote_addr: String,
45
+ pub version: String,
46
+ #[debug(skip)]
47
+ pub(crate) listener: Arc<ListenerInfo>,
48
+ #[debug(skip)]
49
+ pub server: Arc<Server>,
50
+ pub response: ItsiResponse,
51
+ pub start: Instant,
52
+ pub content_type: String,
53
+ }
54
+
55
+ impl fmt::Display for ItsiRequest {
56
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57
+ write!(
58
+ f,
59
+ "{} {} {}",
60
+ self.version().unwrap(),
61
+ self.method().unwrap(),
62
+ self.path().unwrap()
63
+ )
64
+ }
26
65
  }
27
66
 
28
67
  impl ItsiRequest {
29
- pub(crate) async fn build_from(
68
+ pub fn is_connection_closed_err(ruby: &Ruby, err: &Error) -> bool {
69
+ match err.error_type() {
70
+ ErrorType::Jump(_) => false,
71
+ ErrorType::Error(_, _) => false,
72
+ ErrorType::Exception(exception) => {
73
+ exception.is_kind_of(ruby.exception_eof_error())
74
+ && err
75
+ .value()
76
+ .map(|v| {
77
+ v.funcall::<_, _, String>(*ID_MESSAGE, ())
78
+ .unwrap_or("".to_string())
79
+ .eq(CLIENT_CONNECTION_CLOSED)
80
+ })
81
+ .unwrap_or(false)
82
+ }
83
+ }
84
+ }
85
+
86
+ pub fn is_json(&self) -> bool {
87
+ self.content_type.eq("application/json")
88
+ }
89
+
90
+ pub fn is_html(&self) -> bool {
91
+ self.content_type.eq("text/html")
92
+ }
93
+
94
+ pub fn process(
95
+ self,
96
+ ruby: &Ruby,
97
+ server: RClass,
98
+ app: Opaque<Value>,
99
+ ) -> magnus::error::Result<()> {
100
+ let req = format!("{}", self);
101
+ let response = self.response.clone();
102
+ let start = self.start;
103
+ debug!("{} Started", req);
104
+ let result = server.funcall::<_, _, Value>(*ID_CALL, (app, self));
105
+ if let Err(err) = result {
106
+ Self::internal_error(ruby, response, err);
107
+ }
108
+ debug!("{} Finished in {:?}", req, start.elapsed());
109
+
110
+ Ok(())
111
+ }
112
+
113
+ pub fn internal_error(ruby: &Ruby, response: ItsiResponse, err: Error) {
114
+ if Self::is_connection_closed_err(ruby, &err) {
115
+ debug!("Connection closed by client");
116
+ response.close();
117
+ } else if let Some(rb_err) = err.value() {
118
+ let backtrace = rb_err
119
+ .funcall::<_, _, Vec<String>>(*ID_BACKTRACE, ())
120
+ .unwrap_or_default();
121
+
122
+ error!("Error occurred in Handler: {:?}", rb_err);
123
+ for line in backtrace {
124
+ error!("{}", line);
125
+ }
126
+ response.internal_server_error(err.to_string());
127
+ } else {
128
+ response.internal_server_error(err.to_string());
129
+ }
130
+ }
131
+
132
+ pub fn error(self, message: String) {
133
+ self.response.internal_server_error(message);
134
+ }
135
+
136
+ pub(crate) async fn process_request(
137
+ hyper_request: Request<Incoming>,
138
+ sender: async_channel::Sender<RequestJob>,
139
+ server: Arc<Server>,
140
+ listener: Arc<ListenerInfo>,
141
+ addr: SockAddr,
142
+ shutdown_rx: watch::Receiver<RunningPhase>,
143
+ ) -> itsi_error::Result<Response<BoxBody<Bytes, Infallible>>> {
144
+ let (request, mut receiver) = ItsiRequest::new(hyper_request, addr, server, listener).await;
145
+
146
+ let response = request.response.clone();
147
+ match sender.send(RequestJob::ProcessRequest(request)).await {
148
+ Err(err) => {
149
+ error!("Error occurred: {}", err);
150
+ let mut response = Response::new(BoxBody::new(Empty::new()));
151
+ *response.status_mut() = StatusCode::BAD_REQUEST;
152
+ Ok(response)
153
+ }
154
+ _ => match receiver.recv().await {
155
+ Some(first_frame) => Ok(response.build(first_frame, receiver, shutdown_rx).await),
156
+ None => Ok(response.build(None, receiver, shutdown_rx).await),
157
+ },
158
+ }
159
+ }
160
+
161
+ pub(crate) async fn new(
30
162
  request: Request<Incoming>,
31
163
  sock_addr: SockAddr,
32
- script_name: String,
33
- listener: Arc<Listener>,
34
- ) -> Self {
164
+ server: Arc<Server>,
165
+ listener: Arc<ListenerInfo>,
166
+ ) -> (ItsiRequest, mpsc::Receiver<Option<Bytes>>) {
35
167
  let (parts, body) = request.into_parts();
36
- let method = parts.method.to_string();
37
- let port = parts.uri.port_u16().unwrap_or(listener.port());
38
- let query_string = parts.uri.query().unwrap_or("").to_string();
39
- let rack_protocol = parts
40
- .headers
41
- .get("upgrade")
42
- .or_else(|| parts.headers.get("protocol"))
43
- .map(|value| {
44
- value
168
+ let body = if server.stream_body.is_some_and(|f| f) {
169
+ ItsiBody::Stream(ItsiBodyProxy::new(body))
170
+ } else {
171
+ let mut body_bytes = BigBytes::new();
172
+ let mut stream = body.into_data_stream();
173
+ while let Some(chunk) = stream.next().await {
174
+ let byte_array = chunk.unwrap().to_vec();
175
+ body_bytes.write_all(&byte_array).unwrap();
176
+ }
177
+ ItsiBody::Buffered(body_bytes)
178
+ };
179
+ let response_channel = mpsc::channel::<Option<Bytes>>(100);
180
+ (
181
+ Self {
182
+ remote_addr: sock_addr.to_string(),
183
+ body,
184
+ server,
185
+ listener,
186
+ version: format!("{:?}", &parts.version),
187
+ response: ItsiResponse::new(
188
+ parts.clone(),
189
+ response_channel.0,
190
+ parts
191
+ .headers
192
+ .get("Accept")
193
+ .unwrap_or(&HeaderValue::from_static("text/html"))
194
+ .to_str()
195
+ .unwrap()
196
+ .to_string(),
197
+ ),
198
+ start: Instant::now(),
199
+ content_type: parts
200
+ .headers
201
+ .get("Content-Type")
202
+ .unwrap_or(&HeaderValue::from_static(
203
+ "application/x-www-form-urlencoded",
204
+ ))
45
205
  .to_str()
46
- .unwrap_or("")
47
- .split(',')
48
- .map(|s| s.trim().to_owned())
49
- .collect::<Vec<String>>()
50
- })
51
- .unwrap_or_else(|| vec!["http".to_string()]);
52
-
53
- let host = parts
54
- .uri
55
- .host()
56
- .map(ToOwned::to_owned)
57
- .unwrap_or_else(|| listener.host());
58
-
59
- let scheme = parts
60
- .uri
61
- .scheme()
62
- .map(|s| s.to_string())
63
- .unwrap_or_else(|| listener.scheme());
64
-
65
- let headers = parts
66
- .headers
67
- .iter()
68
- .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
69
- .collect();
206
+ .unwrap()
207
+ .to_string(),
208
+ parts,
209
+ },
210
+ response_channel.1,
211
+ )
212
+ }
70
213
 
71
- let path = parts
214
+ pub(crate) fn path(&self) -> MagnusResult<&str> {
215
+ Ok(self
216
+ .parts
72
217
  .uri
73
218
  .path()
74
- .strip_prefix(&script_name)
75
- .unwrap_or(parts.uri.path())
76
- .to_string();
77
-
78
- let version = format!("{:?}", parts.version);
79
- let body = body.collect().await.unwrap().to_bytes();
80
-
81
- Self {
82
- remote_addr: sock_addr.to_string(),
83
- body,
84
- parts,
85
- script_name,
86
- query_string,
87
- method,
88
- headers,
89
- path,
90
- version,
91
- rack_protocol,
92
- host,
93
- scheme,
94
- port,
95
- }
219
+ .strip_prefix(&self.server.script_name)
220
+ .unwrap_or(self.parts.uri.path()))
96
221
  }
97
- }
98
222
 
99
- impl ItsiRequest {
100
- pub(crate) fn path(&self) -> Result<String> {
101
- Ok(self.path.clone())
223
+ pub(crate) fn script_name(&self) -> MagnusResult<&str> {
224
+ Ok(&self.server.script_name)
102
225
  }
103
226
 
104
- pub(crate) fn script_name(&self) -> Result<String> {
105
- Ok(self.script_name.clone())
227
+ pub(crate) fn query_string(&self) -> MagnusResult<&str> {
228
+ Ok(self.parts.uri.query().unwrap_or(""))
106
229
  }
107
230
 
108
- pub(crate) fn query_string(&self) -> Result<String> {
109
- Ok(self.query_string.clone())
231
+ pub(crate) fn method(&self) -> MagnusResult<&str> {
232
+ Ok(self.parts.method.as_str())
110
233
  }
111
234
 
112
- pub(crate) fn method(&self) -> Result<String> {
113
- Ok(self.method.clone())
235
+ pub(crate) fn version(&self) -> MagnusResult<&str> {
236
+ Ok(&self.version)
114
237
  }
115
238
 
116
- pub(crate) fn version(&self) -> Result<String> {
117
- Ok(self.version.clone())
239
+ pub(crate) fn rack_protocol(&self) -> MagnusResult<Vec<&str>> {
240
+ Ok(self
241
+ .parts
242
+ .headers
243
+ .get("upgrade")
244
+ .or_else(|| self.parts.headers.get("protocol"))
245
+ .map(|value| {
246
+ value
247
+ .to_str()
248
+ .unwrap_or("")
249
+ .split(',')
250
+ .map(|s| s.trim())
251
+ .collect::<Vec<&str>>()
252
+ })
253
+ .unwrap_or_else(|| vec!["http"]))
118
254
  }
119
255
 
120
- pub(crate) fn rack_protocol(&self) -> Result<Vec<String>> {
121
- Ok(self.rack_protocol.clone())
256
+ pub(crate) fn host(&self) -> MagnusResult<String> {
257
+ Ok(self
258
+ .parts
259
+ .uri
260
+ .host()
261
+ .map(|host| host.to_string())
262
+ .unwrap_or_else(|| self.listener.host.clone()))
263
+ }
264
+
265
+ pub(crate) fn scheme(&self) -> MagnusResult<String> {
266
+ Ok(self
267
+ .parts
268
+ .uri
269
+ .scheme()
270
+ .map(|scheme| scheme.to_string())
271
+ .unwrap_or_else(|| self.listener.scheme.clone()))
122
272
  }
123
273
 
124
- pub(crate) fn host(&self) -> Result<String> {
125
- Ok(self.host.clone())
274
+ pub(crate) fn headers(&self) -> MagnusResult<Vec<(String, &str)>> {
275
+ Ok(self
276
+ .parts
277
+ .headers
278
+ .iter()
279
+ .map(|(hn, hv)| {
280
+ let key = match hn.as_str() {
281
+ "content-length" => "CONTENT_LENGTH".to_string(),
282
+ "content-type" => "CONTENT_TYPE".to_string(),
283
+ _ => format!("HTTP_{}", hn.as_str().to_uppercase().replace("-", "_")),
284
+ };
285
+ (key, hv.to_str().unwrap_or(""))
286
+ })
287
+ .collect())
126
288
  }
127
289
 
128
- pub(crate) fn headers(&self) -> Result<HashMap<String, String>> {
129
- Ok(self.headers.clone())
290
+ pub(crate) fn remote_addr(&self) -> MagnusResult<&str> {
291
+ Ok(&self.remote_addr)
130
292
  }
131
293
 
132
- pub(crate) fn remote_addr(&self) -> Result<String> {
133
- Ok(self.remote_addr.clone())
294
+ pub(crate) fn port(&self) -> MagnusResult<u16> {
295
+ Ok(self.parts.uri.port_u16().unwrap_or(self.listener.port))
134
296
  }
135
297
 
136
- pub(crate) fn port(&self) -> Result<u16> {
137
- Ok(self.port)
298
+ pub(crate) fn body(&self) -> MagnusResult<Value> {
299
+ Ok(self.body.into_value())
138
300
  }
139
301
 
140
- pub(crate) fn body(&self) -> Result<Bytes> {
141
- Ok(self.body.clone())
302
+ pub(crate) fn response(&self) -> MagnusResult<ItsiResponse> {
303
+ Ok(self.response.clone())
142
304
  }
143
305
  }