itsi-scheduler 0.1.5 → 0.1.14

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-scheduler might be problematic. Click here for more details.

Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CODE_OF_CONDUCT.md +7 -0
  3. data/Cargo.lock +83 -22
  4. data/README.md +5 -0
  5. data/_index.md +7 -0
  6. data/ext/itsi_error/src/from.rs +26 -29
  7. data/ext/itsi_error/src/lib.rs +10 -1
  8. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  9. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  10. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  11. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  12. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  13. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  14. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  15. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  16. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  17. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  18. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  19. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  20. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  21. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  22. data/ext/itsi_rb_helpers/Cargo.toml +1 -0
  23. data/ext/itsi_rb_helpers/src/heap_value.rs +18 -0
  24. data/ext/itsi_rb_helpers/src/lib.rs +59 -9
  25. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  26. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  27. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  28. data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  29. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  30. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  31. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  32. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  33. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  34. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  35. data/ext/itsi_server/Cargo.lock +2956 -0
  36. data/ext/itsi_server/Cargo.toml +69 -26
  37. data/ext/itsi_server/src/env.rs +43 -0
  38. data/ext/itsi_server/src/lib.rs +81 -75
  39. data/ext/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
  40. data/ext/itsi_server/src/{body_proxy/itsi_body_proxy.rs → ruby_types/itsi_body_proxy/mod.rs} +22 -3
  41. data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
  42. data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
  43. data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
  44. data/ext/itsi_server/src/{request/itsi_request.rs → ruby_types/itsi_http_request.rs} +108 -103
  45. data/ext/itsi_server/src/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +79 -38
  46. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  47. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
  48. data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
  49. data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
  50. data/ext/itsi_server/src/server/bind.rs +33 -20
  51. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  52. data/ext/itsi_server/src/server/cache_store.rs +74 -0
  53. data/ext/itsi_server/src/server/itsi_service.rs +172 -0
  54. data/ext/itsi_server/src/server/lifecycle_event.rs +3 -0
  55. data/ext/itsi_server/src/server/listener.rs +197 -106
  56. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
  57. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
  58. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
  59. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
  60. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +264 -0
  61. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
  62. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
  63. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
  64. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
  65. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
  66. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
  67. data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
  68. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
  69. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
  70. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  71. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
  72. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
  73. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
  74. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  75. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
  76. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
  77. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
  78. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
  79. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
  80. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  81. data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
  82. data/ext/itsi_server/src/server/mod.rs +8 -1
  83. data/ext/itsi_server/src/server/process_worker.rs +44 -11
  84. data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
  85. data/ext/itsi_server/src/server/request_job.rs +11 -0
  86. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +129 -46
  87. data/ext/itsi_server/src/server/serve_strategy/mod.rs +9 -6
  88. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +337 -163
  89. data/ext/itsi_server/src/server/signal.rs +25 -2
  90. data/ext/itsi_server/src/server/static_file_server.rs +984 -0
  91. data/ext/itsi_server/src/server/thread_worker.rs +164 -88
  92. data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +55 -17
  93. data/ext/itsi_server/src/server/tls.rs +104 -28
  94. data/ext/itsi_server/src/server/types.rs +43 -0
  95. data/ext/itsi_tracing/Cargo.toml +1 -0
  96. data/ext/itsi_tracing/src/lib.rs +222 -34
  97. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  98. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  99. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  100. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  101. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  102. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  103. data/lib/itsi/scheduler/version.rb +1 -1
  104. data/lib/itsi/scheduler.rb +2 -2
  105. metadata +79 -14
  106. data/ext/itsi_server/extconf.rb +0 -6
  107. data/ext/itsi_server/src/body_proxy/mod.rs +0 -2
  108. data/ext/itsi_server/src/request/mod.rs +0 -1
  109. data/ext/itsi_server/src/response/mod.rs +0 -1
  110. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
  111. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
  112. data/ext/itsi_server/src/server/itsi_server.rs +0 -244
@@ -0,0 +1,216 @@
1
+ use std::{collections::HashMap, sync::Arc};
2
+
3
+ use crate::server::{
4
+ byte_frame::ByteFrame, serve_strategy::single_mode::RunningPhase, types::HttpResponse,
5
+ };
6
+ use bytes::Bytes;
7
+ use derive_more::Debug;
8
+ use futures::{executor::block_on, stream::unfold};
9
+ use http::{
10
+ header::{HeaderName, HeaderValue, CONTENT_TYPE},
11
+ HeaderMap, Response,
12
+ };
13
+ use http_body_util::{combinators::BoxBody, BodyDataStream, BodyExt, Empty, Full, StreamBody};
14
+ use hyper::body::{Frame, Incoming};
15
+ use magnus::error::Result as MagnusResult;
16
+ use parking_lot::Mutex;
17
+ use tokio::sync::{
18
+ mpsc::{Receiver, Sender},
19
+ oneshot, watch,
20
+ };
21
+ use tokio_stream::{wrappers::ReceiverStream, StreamExt};
22
+ use tracing::{error, info, warn};
23
+
24
+ #[derive(Debug, Clone)]
25
+ #[magnus::wrap(class = "Itsi::GrpcStream", free_immediately, size)]
26
+ pub struct ItsiGrpcStream {
27
+ pub inner: Arc<Mutex<ItsiGrpcStreamInner>>,
28
+ }
29
+
30
+ #[derive(Debug)]
31
+ pub struct ItsiGrpcStreamInner {
32
+ pub body: BodyDataStream<Incoming>,
33
+ pub buf: Vec<u8>,
34
+ pub response_sender: Sender<ByteFrame>,
35
+ pub response: Option<HttpResponse>,
36
+ trailer_tx: oneshot::Sender<HeaderMap>,
37
+ trailer_rx: Option<oneshot::Receiver<HeaderMap>>,
38
+ }
39
+
40
+ impl ItsiGrpcStreamInner {
41
+ pub fn read(&mut self, bytes: usize) -> MagnusResult<Bytes> {
42
+ let stream = &mut self.body;
43
+ let buf = &mut self.buf;
44
+ let mut result = Vec::with_capacity(bytes);
45
+
46
+ info!("Entering read with {:?}. Current buf is {:?}", bytes, buf);
47
+
48
+ // First, use any data already in the buffer
49
+ if !buf.is_empty() {
50
+ let remaining = bytes.min(buf.len());
51
+ result.extend_from_slice(&buf[..remaining]);
52
+ buf.drain(..remaining);
53
+ }
54
+
55
+ while result.len() < bytes {
56
+ if let Some(chunk) = block_on(stream.next()) {
57
+ let chunk = chunk.map_err(|err| {
58
+ magnus::Error::new(
59
+ magnus::exception::exception(),
60
+ format!("Error reading body {:?}", err),
61
+ )
62
+ })?;
63
+ let remaining = bytes - result.len();
64
+ if chunk.len() > remaining {
65
+ result.extend_from_slice(&chunk[..remaining]);
66
+ buf.extend_from_slice(&chunk[remaining..]);
67
+ } else {
68
+ result.extend_from_slice(&chunk);
69
+ }
70
+ } else {
71
+ break;
72
+ }
73
+ }
74
+
75
+ Ok(result.into())
76
+ }
77
+
78
+ pub fn write(&mut self, bytes: Bytes) -> MagnusResult<()> {
79
+ self.response_sender
80
+ .blocking_send(ByteFrame::Data(bytes))
81
+ .map_err(|err| {
82
+ magnus::Error::new(
83
+ magnus::exception::exception(),
84
+ format!("Error writing body {:?}", err),
85
+ )
86
+ })?;
87
+ Ok(())
88
+ }
89
+
90
+ pub fn flush(&mut self) -> MagnusResult<()> {
91
+ Ok(())
92
+ }
93
+
94
+ pub fn send_trailers(&mut self, trailers: HashMap<String, String>) -> MagnusResult<()> {
95
+ let mut header_map = HeaderMap::new();
96
+ for (key, value) in trailers {
97
+ if let (Ok(hn), Ok(hv)) = (key.parse::<HeaderName>(), value.parse::<HeaderValue>()) {
98
+ header_map.insert(hn, hv);
99
+ }
100
+ }
101
+ let trailer_tx = std::mem::replace(&mut self.trailer_tx, oneshot::channel().0);
102
+ trailer_tx.send(header_map).map_err(|err| {
103
+ magnus::Error::new(
104
+ magnus::exception::exception(),
105
+ format!("Error sending trailers {:?}", err),
106
+ )
107
+ })?;
108
+ self.response_sender
109
+ .blocking_send(ByteFrame::Empty)
110
+ .map_err(|err| {
111
+ magnus::Error::new(
112
+ magnus::exception::exception(),
113
+ format!("Error flushing {:?}", err),
114
+ )
115
+ })?;
116
+ Ok(())
117
+ }
118
+ }
119
+
120
+ impl ItsiGrpcStream {
121
+ pub fn new(response_sender: Sender<ByteFrame>, body: BodyDataStream<Incoming>) -> Self {
122
+ let (trailer_tx, trailer_rx) = oneshot::channel::<HeaderMap>();
123
+ ItsiGrpcStream {
124
+ inner: Arc::new(Mutex::new(ItsiGrpcStreamInner {
125
+ body,
126
+ buf: Vec::new(),
127
+ response_sender,
128
+ response: Some(Response::new(BoxBody::new(Empty::new()))),
129
+ trailer_tx,
130
+ trailer_rx: Some(trailer_rx),
131
+ })),
132
+ }
133
+ }
134
+
135
+ pub fn read(&self, bytes: usize) -> MagnusResult<Bytes> {
136
+ self.inner.lock().read(bytes)
137
+ }
138
+
139
+ pub fn write(&self, bytes: Bytes) -> MagnusResult<()> {
140
+ self.inner.lock().write(bytes)
141
+ }
142
+
143
+ pub fn flush(&self) -> MagnusResult<()> {
144
+ self.inner.lock().flush()
145
+ }
146
+
147
+ pub fn send_trailers(&self, trailers: HashMap<String, String>) -> MagnusResult<()> {
148
+ self.inner.lock().send_trailers(trailers)
149
+ }
150
+
151
+ pub async fn build_response(
152
+ &self,
153
+ first_frame: ByteFrame,
154
+ receiver: Receiver<ByteFrame>,
155
+ shutdown_rx: watch::Receiver<RunningPhase>,
156
+ ) -> HttpResponse {
157
+ let mut response = self.inner.lock().response.take().unwrap();
158
+ let rx = self.inner.lock().trailer_rx.take().unwrap();
159
+ response
160
+ .headers_mut()
161
+ .append(CONTENT_TYPE, "application/grpc".parse().unwrap());
162
+ *response.body_mut() = if matches!(first_frame, ByteFrame::Empty) {
163
+ BoxBody::new(Empty::new())
164
+ } else if matches!(first_frame, ByteFrame::End(_)) {
165
+ BoxBody::new(Full::new(first_frame.into()))
166
+ } else {
167
+ let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame))));
168
+ let frame_stream = unfold(
169
+ (ReceiverStream::new(receiver), shutdown_rx),
170
+ |(mut receiver, mut shutdown_rx)| async move {
171
+ if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
172
+ return None;
173
+ }
174
+ loop {
175
+ tokio::select! {
176
+ maybe_bytes = receiver.next() => {
177
+ match maybe_bytes {
178
+ Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => {
179
+ return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx)));
180
+ }
181
+ _ => {
182
+ return None;
183
+ }
184
+ }
185
+ },
186
+ _ = shutdown_rx.changed() => {
187
+ match *shutdown_rx.borrow() {
188
+ RunningPhase::ShutdownPending => {
189
+ warn!("Disconnecting streaming client.");
190
+ return None;
191
+ },
192
+ _ => continue,
193
+ }
194
+ }
195
+ }
196
+ }
197
+ },
198
+ );
199
+
200
+ let combined_stream = initial_frame.chain(frame_stream);
201
+ BoxBody::new(StreamBody::new(combined_stream))
202
+ }
203
+ .with_trailers(async move {
204
+ match rx.await {
205
+ Ok(trailers) => Some(Ok(trailers)),
206
+ Err(_err) => None,
207
+ }
208
+ })
209
+ .boxed();
210
+ response
211
+ }
212
+
213
+ pub fn internal_server_error(&self, message: String) {
214
+ error!(message);
215
+ }
216
+ }
@@ -1,57 +1,49 @@
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::{SockAddr, TokioListener},
10
- serve_strategy::single_mode::RunningPhase,
11
- },
12
- };
13
- use bytes::Bytes;
14
1
  use derive_more::Debug;
15
2
  use futures::StreamExt;
16
- use http::{request::Parts, Response, StatusCode};
3
+ use http::{request::Parts, Response, StatusCode, Version};
17
4
  use http_body_util::{combinators::BoxBody, BodyExt, Empty};
18
- use hyper::{body::Incoming, Request};
19
5
  use itsi_error::from::CLIENT_CONNECTION_CLOSED;
6
+ use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
20
7
  use itsi_tracing::{debug, error};
21
8
  use magnus::{
9
+ block::Proc,
22
10
  error::{ErrorType, Result as MagnusResult},
23
11
  Error,
24
12
  };
25
13
  use magnus::{
26
- value::{LazyId, Opaque, ReprValue},
27
- RClass, Ruby, Value,
14
+ value::{LazyId, ReprValue},
15
+ Ruby, Value,
16
+ };
17
+ use std::{fmt, io::Write, sync::Arc, time::Instant};
18
+ use tokio::sync::mpsc::{self};
19
+
20
+ use super::{
21
+ itsi_body_proxy::{big_bytes::BigBytes, ItsiBody, ItsiBodyProxy},
22
+ itsi_http_response::ItsiHttpResponse,
28
23
  };
29
- use std::{convert::Infallible, fmt, io::Write, sync::Arc, time::Instant};
30
- use tokio::sync::{
31
- mpsc::{self},
32
- watch,
24
+ use crate::server::{
25
+ byte_frame::ByteFrame,
26
+ itsi_service::RequestContext,
27
+ request_job::RequestJob,
28
+ types::{HttpRequest, HttpResponse},
33
29
  };
34
- static ID_CALL: LazyId = LazyId::new("call");
30
+
35
31
  static ID_MESSAGE: LazyId = LazyId::new("message");
36
- static ID_BACKTRACE: LazyId = LazyId::new("backtrace");
37
32
 
38
33
  #[derive(Debug)]
39
- #[magnus::wrap(class = "Itsi::Request", free_immediately, size)]
40
- pub struct ItsiRequest {
34
+ #[magnus::wrap(class = "Itsi::HttpRequest", free_immediately, size)]
35
+ pub struct ItsiHttpRequest {
41
36
  pub parts: Parts,
42
37
  #[debug(skip)]
43
38
  pub body: ItsiBody,
44
- pub remote_addr: String,
45
- pub version: String,
46
- #[debug(skip)]
47
- pub(crate) listener: Arc<TokioListener>,
48
- #[debug(skip)]
49
- pub server: Arc<Server>,
50
- pub response: ItsiResponse,
39
+ pub version: Version,
40
+ pub response: ItsiHttpResponse,
51
41
  pub start: Instant,
42
+ #[debug(skip)]
43
+ pub context: RequestContext,
52
44
  }
53
45
 
54
- impl fmt::Display for ItsiRequest {
46
+ impl fmt::Display for ItsiHttpRequest {
55
47
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56
48
  write!(
57
49
  f,
@@ -63,7 +55,7 @@ impl fmt::Display for ItsiRequest {
63
55
  }
64
56
  }
65
57
 
66
- impl ItsiRequest {
58
+ impl ItsiHttpRequest {
67
59
  pub fn is_connection_closed_err(ruby: &Ruby, err: &Error) -> bool {
68
60
  match err.error_type() {
69
61
  ErrorType::Jump(_) => false,
@@ -81,39 +73,37 @@ impl ItsiRequest {
81
73
  }
82
74
  }
83
75
  }
76
+ fn content_type_str(&self) -> &str {
77
+ self.parts
78
+ .headers
79
+ .get("Content-Type")
80
+ .and_then(|hv| hv.to_str().ok())
81
+ .unwrap_or("application/x-www-form-urlencoded")
82
+ }
83
+
84
+ pub fn is_json(&self) -> bool {
85
+ self.content_type_str() == "application/json"
86
+ }
84
87
 
85
- pub fn process(
86
- self,
87
- ruby: &Ruby,
88
- server: RClass,
89
- app: Opaque<Value>,
90
- ) -> magnus::error::Result<()> {
91
- let req = format!("{}", self);
88
+ pub fn is_html(&self) -> bool {
89
+ self.content_type_str() == "text/html"
90
+ }
91
+
92
+ pub fn process(self, ruby: &Ruby, app_proc: Arc<HeapValue<Proc>>) -> magnus::error::Result<()> {
92
93
  let response = self.response.clone();
93
- let start = self.start;
94
- debug!("{} Started", req);
95
- let result = server.funcall::<_, _, Value>(*ID_CALL, (app, self));
94
+ let result = app_proc.call::<_, Value>((self,));
96
95
  if let Err(err) = result {
97
96
  Self::internal_error(ruby, response, err);
98
97
  }
99
- debug!("{} Finished in {:?}", req, start.elapsed());
100
-
101
98
  Ok(())
102
99
  }
103
100
 
104
- pub fn internal_error(ruby: &Ruby, response: ItsiResponse, err: Error) {
101
+ pub fn internal_error(ruby: &Ruby, response: ItsiHttpResponse, err: Error) {
105
102
  if Self::is_connection_closed_err(ruby, &err) {
106
103
  debug!("Connection closed by client");
107
104
  response.close();
108
105
  } else if let Some(rb_err) = err.value() {
109
- let backtrace = rb_err
110
- .funcall::<_, _, Vec<String>>(*ID_BACKTRACE, ())
111
- .unwrap_or_default();
112
-
113
- error!("Error occurred in Handler: {:?}", rb_err);
114
- for line in backtrace {
115
- error!("{}", line);
116
- }
106
+ print_rb_backtrace(rb_err);
117
107
  response.internal_server_error(err.to_string());
118
108
  } else {
119
109
  response.internal_server_error(err.to_string());
@@ -125,17 +115,18 @@ impl ItsiRequest {
125
115
  }
126
116
 
127
117
  pub(crate) async fn process_request(
128
- hyper_request: Request<Incoming>,
129
- sender: async_channel::Sender<RequestJob>,
130
- server: Arc<Server>,
131
- listener: Arc<TokioListener>,
132
- addr: SockAddr,
133
- shutdown_rx: watch::Receiver<RunningPhase>,
134
- ) -> itsi_error::Result<Response<BoxBody<Bytes, Infallible>>> {
135
- let (request, mut receiver) = ItsiRequest::new(hyper_request, addr, server, listener).await;
136
-
118
+ app: Arc<HeapValue<Proc>>,
119
+ hyper_request: HttpRequest,
120
+ context: &RequestContext,
121
+ ) -> itsi_error::Result<HttpResponse> {
122
+ let (request, mut receiver) = ItsiHttpRequest::new(hyper_request, context).await;
123
+ let shutdown_channel = context.service.shutdown_channel.clone();
137
124
  let response = request.response.clone();
138
- match sender.send(RequestJob::ProcessRequest(request)).await {
125
+ match context
126
+ .sender
127
+ .send(RequestJob::ProcessHttpRequest(request, app))
128
+ .await
129
+ {
139
130
  Err(err) => {
140
131
  error!("Error occurred: {}", err);
141
132
  let mut response = Response::new(BoxBody::new(Empty::new()));
@@ -143,20 +134,22 @@ impl ItsiRequest {
143
134
  Ok(response)
144
135
  }
145
136
  _ => match receiver.recv().await {
146
- Some(first_frame) => Ok(response.build(first_frame, receiver, shutdown_rx).await),
147
- None => Ok(response.build(None, receiver, shutdown_rx).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),
148
143
  },
149
144
  }
150
145
  }
151
146
 
152
147
  pub(crate) async fn new(
153
- request: Request<Incoming>,
154
- sock_addr: SockAddr,
155
- server: Arc<Server>,
156
- listener: Arc<TokioListener>,
157
- ) -> (ItsiRequest, mpsc::Receiver<Option<Bytes>>) {
148
+ request: HttpRequest,
149
+ context: &RequestContext,
150
+ ) -> (ItsiHttpRequest, mpsc::Receiver<ByteFrame>) {
158
151
  let (parts, body) = request.into_parts();
159
- let body = if server.stream_body.is_some_and(|f| f) {
152
+ let body = if context.server_params.streamable_body {
160
153
  ItsiBody::Stream(ItsiBodyProxy::new(body))
161
154
  } else {
162
155
  let mut body_bytes = BigBytes::new();
@@ -167,16 +160,14 @@ impl ItsiRequest {
167
160
  }
168
161
  ItsiBody::Buffered(body_bytes)
169
162
  };
170
- let response_channel = mpsc::channel::<Option<Bytes>>(100);
163
+ let response_channel = mpsc::channel::<ByteFrame>(100);
171
164
  (
172
165
  Self {
173
- remote_addr: sock_addr.to_string(),
174
- body,
175
- server,
176
- listener,
177
- version: format!("{:?}", &parts.version),
178
- response: ItsiResponse::new(parts.clone(), response_channel.0),
166
+ context: context.clone(),
167
+ version: parts.version,
168
+ response: ItsiHttpResponse::new(parts.clone(), response_channel.0),
179
169
  start: Instant::now(),
170
+ body,
180
171
  parts,
181
172
  },
182
173
  response_channel.1,
@@ -188,12 +179,12 @@ impl ItsiRequest {
188
179
  .parts
189
180
  .uri
190
181
  .path()
191
- .strip_prefix(&self.server.script_name)
182
+ .strip_prefix(&self.context.server_params.script_name)
192
183
  .unwrap_or(self.parts.uri.path()))
193
184
  }
194
185
 
195
186
  pub(crate) fn script_name(&self) -> MagnusResult<&str> {
196
- Ok(&self.server.script_name)
187
+ Ok(&self.context.server_params.script_name)
197
188
  }
198
189
 
199
190
  pub(crate) fn query_string(&self) -> MagnusResult<&str> {
@@ -205,7 +196,14 @@ impl ItsiRequest {
205
196
  }
206
197
 
207
198
  pub(crate) fn version(&self) -> MagnusResult<&str> {
208
- Ok(&self.version)
199
+ Ok(match self.version {
200
+ Version::HTTP_09 => "HTTP/0.9",
201
+ Version::HTTP_10 => "HTTP/1.0",
202
+ Version::HTTP_11 => "HTTP/1.1",
203
+ Version::HTTP_2 => "HTTP/2.0",
204
+ Version::HTTP_3 => "HTTP/3.0",
205
+ _ => "HTTP/Unknown",
206
+ })
209
207
  }
210
208
 
211
209
  pub(crate) fn rack_protocol(&self) -> MagnusResult<Vec<&str>> {
@@ -225,53 +223,60 @@ impl ItsiRequest {
225
223
  .unwrap_or_else(|| vec!["http"]))
226
224
  }
227
225
 
228
- pub(crate) fn host(&self) -> MagnusResult<String> {
226
+ pub(crate) fn host(&self) -> MagnusResult<&str> {
229
227
  Ok(self
230
228
  .parts
231
229
  .uri
232
230
  .host()
233
- .map(|host| host.to_string())
234
- .unwrap_or_else(|| self.listener.host()))
231
+ .unwrap_or_else(|| &self.context.listener.host))
235
232
  }
236
233
 
237
- pub(crate) fn scheme(&self) -> MagnusResult<String> {
234
+ pub(crate) fn scheme(&self) -> MagnusResult<&str> {
238
235
  Ok(self
239
236
  .parts
240
237
  .uri
241
238
  .scheme()
242
- .map(|scheme| scheme.to_string())
243
- .unwrap_or_else(|| self.listener.scheme()))
239
+ .map(|scheme| scheme.as_str())
240
+ .unwrap_or_else(|| &self.context.listener.scheme))
244
241
  }
245
242
 
246
- pub(crate) fn headers(&self) -> MagnusResult<Vec<(String, &str)>> {
243
+ pub(crate) fn headers(&self) -> MagnusResult<Vec<(&str, &str)>> {
247
244
  Ok(self
248
245
  .parts
249
246
  .headers
250
247
  .iter()
251
- .map(|(hn, hv)| {
252
- let key = match hn.as_str() {
253
- "content-length" => "CONTENT_LENGTH".to_string(),
254
- "content-type" => "CONTENT_TYPE".to_string(),
255
- _ => format!("HTTP_{}", hn.as_str().to_uppercase().replace("-", "_")),
256
- };
257
- (key, hv.to_str().unwrap_or(""))
258
- })
259
- .collect())
248
+ .map(|(hn, hv)| (hn.as_str(), hv.to_str().unwrap_or("")))
249
+ .collect::<Vec<(&str, &str)>>())
250
+ }
251
+
252
+ pub fn header(&self, name: String) -> MagnusResult<Option<Vec<&str>>> {
253
+ let result: Vec<&str> = self
254
+ .parts
255
+ .headers
256
+ .get_all(&name)
257
+ .iter()
258
+ .filter_map(|value| value.to_str().ok())
259
+ .collect();
260
+ Ok(Some(result))
260
261
  }
261
262
 
262
263
  pub(crate) fn remote_addr(&self) -> MagnusResult<&str> {
263
- Ok(&self.remote_addr)
264
+ Ok(&self.context.addr)
264
265
  }
265
266
 
266
267
  pub(crate) fn port(&self) -> MagnusResult<u16> {
267
- Ok(self.parts.uri.port_u16().unwrap_or(self.listener.port()))
268
+ Ok(self
269
+ .parts
270
+ .uri
271
+ .port_u16()
272
+ .unwrap_or(self.context.listener.port))
268
273
  }
269
274
 
270
- pub(crate) fn body(&self) -> MagnusResult<Value> {
275
+ pub(crate) fn body(&self) -> MagnusResult<Option<Value>> {
271
276
  Ok(self.body.into_value())
272
277
  }
273
278
 
274
- pub(crate) fn response(&self) -> MagnusResult<ItsiResponse> {
279
+ pub(crate) fn response(&self) -> MagnusResult<ItsiHttpResponse> {
275
280
  Ok(self.response.clone())
276
281
  }
277
282
  }