itsi-server 0.1.1 → 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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +7 -0
  4. data/Cargo.lock +3937 -0
  5. data/Cargo.toml +7 -0
  6. data/README.md +4 -0
  7. data/Rakefile +8 -1
  8. data/_index.md +6 -0
  9. data/exe/itsi +141 -46
  10. data/ext/itsi_error/Cargo.toml +3 -0
  11. data/ext/itsi_error/src/lib.rs +98 -24
  12. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  13. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  14. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  15. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  16. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  17. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  18. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  19. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  20. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  21. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  22. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  23. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  24. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  25. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  26. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  27. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  28. data/ext/itsi_rb_helpers/Cargo.toml +3 -0
  29. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  30. data/ext/itsi_rb_helpers/src/lib.rs +140 -10
  31. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  32. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  33. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  34. 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
  35. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  36. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  37. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  38. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  39. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  40. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  41. data/ext/itsi_scheduler/Cargo.toml +24 -0
  42. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  43. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  44. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  45. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  46. data/ext/itsi_scheduler/src/lib.rs +38 -0
  47. data/ext/itsi_server/Cargo.lock +2956 -0
  48. data/ext/itsi_server/Cargo.toml +72 -14
  49. data/ext/itsi_server/extconf.rb +1 -1
  50. data/ext/itsi_server/src/default_responses/html/401.html +68 -0
  51. data/ext/itsi_server/src/default_responses/html/403.html +68 -0
  52. data/ext/itsi_server/src/default_responses/html/404.html +68 -0
  53. data/ext/itsi_server/src/default_responses/html/413.html +71 -0
  54. data/ext/itsi_server/src/default_responses/html/429.html +68 -0
  55. data/ext/itsi_server/src/default_responses/html/500.html +71 -0
  56. data/ext/itsi_server/src/default_responses/html/502.html +71 -0
  57. data/ext/itsi_server/src/default_responses/html/503.html +68 -0
  58. data/ext/itsi_server/src/default_responses/html/504.html +69 -0
  59. data/ext/itsi_server/src/default_responses/html/index.html +238 -0
  60. data/ext/itsi_server/src/default_responses/json/401.json +6 -0
  61. data/ext/itsi_server/src/default_responses/json/403.json +6 -0
  62. data/ext/itsi_server/src/default_responses/json/404.json +6 -0
  63. data/ext/itsi_server/src/default_responses/json/413.json +6 -0
  64. data/ext/itsi_server/src/default_responses/json/429.json +6 -0
  65. data/ext/itsi_server/src/default_responses/json/500.json +6 -0
  66. data/ext/itsi_server/src/default_responses/json/502.json +6 -0
  67. data/ext/itsi_server/src/default_responses/json/503.json +6 -0
  68. data/ext/itsi_server/src/default_responses/json/504.json +6 -0
  69. data/ext/itsi_server/src/default_responses/mod.rs +11 -0
  70. data/ext/itsi_server/src/env.rs +43 -0
  71. data/ext/itsi_server/src/lib.rs +132 -40
  72. data/ext/itsi_server/src/prelude.rs +2 -0
  73. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
  74. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +143 -0
  75. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  76. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
  77. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +345 -0
  78. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +391 -0
  79. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  80. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +375 -0
  81. data/ext/itsi_server/src/ruby_types/itsi_server.rs +83 -0
  82. data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
  83. data/ext/itsi_server/src/server/binds/bind.rs +201 -0
  84. data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
  85. data/ext/itsi_server/src/server/binds/listener.rs +432 -0
  86. data/ext/itsi_server/src/server/binds/mod.rs +4 -0
  87. data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
  88. data/ext/itsi_server/src/server/binds/tls.rs +270 -0
  89. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  90. data/ext/itsi_server/src/server/http_message_types.rs +97 -0
  91. data/ext/itsi_server/src/server/io_stream.rs +105 -0
  92. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +165 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +56 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +87 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +86 -0
  97. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +285 -0
  98. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +142 -0
  99. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +289 -0
  100. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +292 -0
  101. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +55 -0
  102. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
  103. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +157 -0
  104. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +195 -0
  105. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
  106. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +201 -0
  107. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  108. data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  109. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +87 -0
  110. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +414 -0
  111. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +131 -0
  112. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  113. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +44 -0
  114. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +36 -0
  115. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
  116. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +180 -0
  117. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  118. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +163 -0
  119. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  120. data/ext/itsi_server/src/server/middleware_stack/mod.rs +347 -0
  121. data/ext/itsi_server/src/server/mod.rs +12 -5
  122. data/ext/itsi_server/src/server/process_worker.rs +247 -0
  123. data/ext/itsi_server/src/server/request_job.rs +11 -0
  124. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +342 -0
  125. data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
  126. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
  127. data/ext/itsi_server/src/server/signal.rs +76 -0
  128. data/ext/itsi_server/src/server/size_limited_incoming.rs +101 -0
  129. data/ext/itsi_server/src/server/thread_worker.rs +475 -0
  130. data/ext/itsi_server/src/services/cache_store.rs +74 -0
  131. data/ext/itsi_server/src/services/itsi_http_service.rs +239 -0
  132. data/ext/itsi_server/src/services/mime_types.rs +1416 -0
  133. data/ext/itsi_server/src/services/mod.rs +6 -0
  134. data/ext/itsi_server/src/services/password_hasher.rs +83 -0
  135. data/ext/itsi_server/src/services/rate_limiter.rs +569 -0
  136. data/ext/itsi_server/src/services/static_file_server.rs +1324 -0
  137. data/ext/itsi_tracing/Cargo.toml +5 -0
  138. data/ext/itsi_tracing/src/lib.rs +315 -7
  139. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  140. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  141. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  142. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  143. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  144. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  145. data/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
  146. data/lib/itsi/http_request.rb +186 -0
  147. data/lib/itsi/http_response.rb +41 -0
  148. data/lib/itsi/passfile.rb +109 -0
  149. data/lib/itsi/server/config/dsl.rb +565 -0
  150. data/lib/itsi/server/config.rb +166 -0
  151. data/lib/itsi/server/default_app/default_app.rb +34 -0
  152. data/lib/itsi/server/default_app/index.html +115 -0
  153. data/lib/itsi/server/default_config/Itsi-rackup.rb +119 -0
  154. data/lib/itsi/server/default_config/Itsi.rb +107 -0
  155. data/lib/itsi/server/grpc/grpc_call.rb +246 -0
  156. data/lib/itsi/server/grpc/grpc_interface.rb +100 -0
  157. data/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  158. data/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  159. data/lib/itsi/server/rack/handler/itsi.rb +27 -0
  160. data/lib/itsi/server/rack_interface.rb +94 -0
  161. data/lib/itsi/server/route_tester.rb +107 -0
  162. data/lib/itsi/server/scheduler_interface.rb +21 -0
  163. data/lib/itsi/server/scheduler_mode.rb +10 -0
  164. data/lib/itsi/server/signal_trap.rb +29 -0
  165. data/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
  166. data/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
  167. data/lib/itsi/server/typed_handlers.rb +17 -0
  168. data/lib/itsi/server/version.rb +1 -1
  169. data/lib/itsi/server.rb +160 -9
  170. data/lib/itsi/standard_headers.rb +86 -0
  171. data/lib/ruby_lsp/itsi/addon.rb +111 -0
  172. data/lib/shell_completions/completions.rb +26 -0
  173. metadata +182 -25
  174. data/ext/itsi_server/src/request/itsi_request.rs +0 -143
  175. data/ext/itsi_server/src/request/mod.rs +0 -1
  176. data/ext/itsi_server/src/server/bind.rs +0 -138
  177. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  178. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  179. data/ext/itsi_server/src/server/itsi_server.rs +0 -182
  180. data/ext/itsi_server/src/server/listener.rs +0 -218
  181. data/ext/itsi_server/src/server/tls.rs +0 -138
  182. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  183. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
  184. data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,264 @@
1
+ use super::itsi_grpc_call::CompressionAlgorithm;
2
+ use crate::prelude::*;
3
+ use crate::server::http_message_types::HttpResponse;
4
+ use crate::server::size_limited_incoming::SizeLimitedIncoming;
5
+ use crate::server::{byte_frame::ByteFrame, serve_strategy::single_mode::RunningPhase};
6
+ use bytes::Bytes;
7
+ use derive_more::Debug;
8
+ use futures::stream::unfold;
9
+ use http::Version;
10
+ use http::{
11
+ header::{HeaderName, HeaderValue},
12
+ HeaderMap, Response,
13
+ };
14
+ use http_body_util::{combinators::BoxBody, BodyDataStream, BodyExt, Empty, Full, StreamBody};
15
+ use hyper::body::{Frame, Incoming};
16
+ use magnus::error::Result as MagnusResult;
17
+ use nix::unistd::pipe;
18
+ use parking_lot::Mutex;
19
+ use std::sync::atomic::{AtomicBool, Ordering};
20
+ use std::{
21
+ collections::HashMap,
22
+ os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd},
23
+ sync::Arc,
24
+ };
25
+ use tokio::{
26
+ spawn,
27
+ sync::{
28
+ mpsc::{self, Sender},
29
+ oneshot, watch,
30
+ },
31
+ };
32
+ use tokio_stream::{wrappers::ReceiverStream, StreamExt};
33
+
34
+ #[derive(Debug, Clone)]
35
+ #[magnus::wrap(class = "Itsi::GrpcResponseStream", free_immediately, size)]
36
+ pub struct ItsiGrpcResponseStream {
37
+ pub inner: Arc<Mutex<ItsiGrpcResponseStreamInner>>,
38
+ pub cancelled: Arc<AtomicBool>,
39
+ }
40
+
41
+ #[derive(Debug)]
42
+ pub struct ItsiGrpcResponseStreamInner {
43
+ pub incoming_reader: Option<OwnedFd>,
44
+ pub buf: Vec<u8>,
45
+ pub response_sender: Sender<ByteFrame>,
46
+ pub response: Option<HttpResponse>,
47
+ pub response_headers: HeaderMap,
48
+ trailer_tx: oneshot::Sender<HeaderMap>,
49
+ trailer_rx: Option<oneshot::Receiver<HeaderMap>>,
50
+ }
51
+
52
+ impl ItsiGrpcResponseStreamInner {
53
+ pub fn reader(&mut self) -> MagnusResult<i32> {
54
+ Ok(self.incoming_reader.take().unwrap().into_raw_fd())
55
+ }
56
+
57
+ pub fn write(&mut self, bytes: Bytes) -> MagnusResult<()> {
58
+ self.response_sender
59
+ .blocking_send(ByteFrame::Data(bytes))
60
+ .map_err(|err| {
61
+ magnus::Error::new(
62
+ magnus::exception::io_error(),
63
+ format!("Trying to write to closed stream: {:?}", err),
64
+ )
65
+ })?;
66
+ Ok(())
67
+ }
68
+
69
+ pub fn flush(&mut self) -> MagnusResult<()> {
70
+ Ok(())
71
+ }
72
+
73
+ pub fn send_trailers(&mut self, trailers: HashMap<String, String>) -> MagnusResult<()> {
74
+ let mut header_map = HeaderMap::new();
75
+ for (key, value) in trailers {
76
+ if let (Ok(hn), Ok(hv)) = (key.parse::<HeaderName>(), value.parse::<HeaderValue>()) {
77
+ header_map.insert(hn, hv);
78
+ }
79
+ }
80
+ let trailer_tx = std::mem::replace(&mut self.trailer_tx, oneshot::channel().0);
81
+ trailer_tx.send(header_map).map_err(|err| {
82
+ magnus::Error::new(
83
+ magnus::exception::standard_error(),
84
+ format!("Error sending trailers {:?}", err),
85
+ )
86
+ })?;
87
+ Ok(())
88
+ }
89
+
90
+ pub fn close(&mut self) -> MagnusResult<()> {
91
+ self.response_sender.blocking_send(ByteFrame::Empty).ok();
92
+ Ok(())
93
+ }
94
+
95
+ pub fn add_headers(&mut self, headers: HashMap<Bytes, Vec<Bytes>>) -> MagnusResult<()> {
96
+ for (name, values) in headers {
97
+ let header_name = HeaderName::from_bytes(&name).map_err(|e| {
98
+ itsi_error::ItsiError::InvalidInput(format!(
99
+ "Invalid header name {:?}: {:?}",
100
+ name, e
101
+ ))
102
+ })?;
103
+ for value in values {
104
+ let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
105
+ self.response_headers.insert(&header_name, header_value);
106
+ }
107
+ }
108
+
109
+ Ok(())
110
+ }
111
+ }
112
+
113
+ impl ItsiGrpcResponseStream {
114
+ pub async fn new(
115
+ compression_out: CompressionAlgorithm,
116
+ response_sender: Sender<ByteFrame>,
117
+ mut body: BodyDataStream<SizeLimitedIncoming<Incoming>>,
118
+ ) -> Self {
119
+ let (trailer_tx, trailer_rx) = oneshot::channel::<HeaderMap>();
120
+ let (pipe_read, pipe_write) = pipe().unwrap();
121
+
122
+ nix::fcntl::fcntl(
123
+ pipe_read.as_raw_fd(),
124
+ nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::O_NONBLOCK),
125
+ )
126
+ .unwrap();
127
+
128
+ nix::fcntl::fcntl(
129
+ pipe_write.as_raw_fd(),
130
+ nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::O_NONBLOCK),
131
+ )
132
+ .unwrap();
133
+
134
+ let pipe_raw_fd = pipe_write.into_raw_fd();
135
+
136
+ let cancelled = Arc::new(AtomicBool::new(false));
137
+ let cancelled_clone = cancelled.clone();
138
+ spawn(async move {
139
+ use std::io::Write;
140
+ let mut write_end = unsafe { std::fs::File::from_raw_fd(pipe_raw_fd) };
141
+ while let Some(Ok(body)) = body.next().await {
142
+ write_end.write_all(&body).unwrap();
143
+ }
144
+ cancelled_clone.store(true, Ordering::SeqCst);
145
+ });
146
+
147
+ let mut response_headers = HeaderMap::new();
148
+
149
+ match compression_out {
150
+ CompressionAlgorithm::None => (),
151
+ CompressionAlgorithm::Deflate => {
152
+ response_headers.insert("grpc-encoding", "deflate".parse().unwrap());
153
+ }
154
+ CompressionAlgorithm::Gzip => {
155
+ response_headers.insert("grpc-encoding", "gzip".parse().unwrap());
156
+ }
157
+ }
158
+ ItsiGrpcResponseStream {
159
+ inner: Arc::new(Mutex::new(ItsiGrpcResponseStreamInner {
160
+ buf: Vec::new(),
161
+ response_headers,
162
+ incoming_reader: Some(pipe_read),
163
+ response_sender,
164
+ response: Some(Response::new(BoxBody::new(Empty::new()))),
165
+ trailer_tx,
166
+ trailer_rx: Some(trailer_rx),
167
+ })),
168
+ cancelled,
169
+ }
170
+ }
171
+
172
+ pub fn reader(&self) -> MagnusResult<i32> {
173
+ self.inner.lock().reader()
174
+ }
175
+
176
+ pub fn write(&self, bytes: Bytes) -> MagnusResult<()> {
177
+ self.inner.lock().write(bytes)
178
+ }
179
+
180
+ pub fn flush(&self) -> MagnusResult<()> {
181
+ self.inner.lock().flush()
182
+ }
183
+
184
+ pub fn is_cancelled(&self) -> MagnusResult<bool> {
185
+ Ok(self.cancelled.load(Ordering::SeqCst))
186
+ }
187
+
188
+ pub fn send_trailers(&self, trailers: HashMap<String, String>) -> MagnusResult<()> {
189
+ self.inner.lock().send_trailers(trailers)
190
+ }
191
+
192
+ pub fn close(&self) -> MagnusResult<()> {
193
+ self.inner.lock().close()
194
+ }
195
+
196
+ pub fn add_headers(&self, headers: HashMap<Bytes, Vec<Bytes>>) -> MagnusResult<()> {
197
+ self.inner.lock().add_headers(headers)
198
+ }
199
+
200
+ pub async fn build_response(
201
+ &self,
202
+ first_frame: ByteFrame,
203
+ receiver: mpsc::Receiver<ByteFrame>,
204
+ shutdown_rx: watch::Receiver<RunningPhase>,
205
+ ) -> HttpResponse {
206
+ let mut response = self.inner.lock().response.take().unwrap();
207
+ let rx = self.inner.lock().trailer_rx.take().unwrap();
208
+ *response.version_mut() = Version::HTTP_2;
209
+ *response.headers_mut() = self.inner.lock().response_headers.clone();
210
+ *response.body_mut() = if matches!(first_frame, ByteFrame::Empty) {
211
+ BoxBody::new(Empty::new())
212
+ } else if matches!(first_frame, ByteFrame::End(_)) {
213
+ BoxBody::new(Full::new(first_frame.into()))
214
+ } else {
215
+ let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame))));
216
+ let frame_stream = unfold(
217
+ (ReceiverStream::new(receiver), shutdown_rx),
218
+ |(mut receiver, mut shutdown_rx)| async move {
219
+ if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
220
+ return None;
221
+ }
222
+ loop {
223
+ tokio::select! {
224
+ maybe_bytes = receiver.next() => {
225
+ match maybe_bytes {
226
+ Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => {
227
+ return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx)));
228
+ }
229
+ _ => {
230
+ return None;
231
+ }
232
+ }
233
+ },
234
+ _ = shutdown_rx.changed() => {
235
+ match *shutdown_rx.borrow() {
236
+ RunningPhase::ShutdownPending => {
237
+ warn!("Disconnecting streaming client.");
238
+ return None;
239
+ },
240
+ _ => continue,
241
+ }
242
+ }
243
+ }
244
+ }
245
+ },
246
+ );
247
+
248
+ let combined_stream = initial_frame.chain(frame_stream);
249
+ BoxBody::new(StreamBody::new(combined_stream))
250
+ }
251
+ .with_trailers(async move {
252
+ match rx.await {
253
+ Ok(trailers) => Some(Ok(trailers)),
254
+ Err(_err) => None,
255
+ }
256
+ })
257
+ .boxed();
258
+ response
259
+ }
260
+
261
+ pub fn internal_server_error(&self, message: String) {
262
+ error!(message);
263
+ }
264
+ }
@@ -0,0 +1,345 @@
1
+ use derive_more::Debug;
2
+ use futures::StreamExt;
3
+ use http::{header::CONTENT_LENGTH, request::Parts, Response, StatusCode, Version};
4
+ use http_body_util::{combinators::BoxBody, BodyExt, Empty};
5
+ use itsi_error::CLIENT_CONNECTION_CLOSED;
6
+ use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
7
+ use itsi_tracing::{debug, error};
8
+ use magnus::{
9
+ block::Proc,
10
+ error::{ErrorType, Result as MagnusResult},
11
+ Error, RHash, Symbol,
12
+ };
13
+ use magnus::{
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,
23
+ };
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,
32
+ };
33
+
34
+ static ID_MESSAGE: LazyId = LazyId::new("message");
35
+
36
+ #[derive(Debug)]
37
+ #[magnus::wrap(class = "Itsi::HttpRequest", free_immediately, size)]
38
+ pub struct ItsiHttpRequest {
39
+ pub parts: Parts,
40
+ #[debug(skip)]
41
+ pub body: ItsiBody,
42
+ pub version: Version,
43
+ pub response: ItsiHttpResponse,
44
+ pub start: Instant,
45
+ #[debug(skip)]
46
+ pub context: HttpRequestContext,
47
+ pub script_name: String,
48
+ }
49
+
50
+ impl fmt::Display for ItsiHttpRequest {
51
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52
+ write!(
53
+ f,
54
+ "{} {} {}",
55
+ self.version().unwrap(),
56
+ self.method().unwrap(),
57
+ self.path().unwrap()
58
+ )
59
+ }
60
+ }
61
+
62
+ impl ItsiHttpRequest {
63
+ pub fn is_connection_closed_err(ruby: &Ruby, err: &Error) -> bool {
64
+ match err.error_type() {
65
+ ErrorType::Jump(_) => false,
66
+ ErrorType::Error(_, _) => false,
67
+ ErrorType::Exception(exception) => {
68
+ exception.is_kind_of(ruby.exception_eof_error())
69
+ && err
70
+ .value()
71
+ .map(|v| {
72
+ v.funcall::<_, _, String>(*ID_MESSAGE, ())
73
+ .unwrap_or("".to_string())
74
+ .eq(CLIENT_CONNECTION_CLOSED)
75
+ })
76
+ .unwrap_or(false)
77
+ }
78
+ }
79
+ }
80
+ pub fn content_type_str(&self) -> &str {
81
+ self.parts
82
+ .headers
83
+ .get("Content-Type")
84
+ .and_then(|hv| hv.to_str().ok())
85
+ .unwrap_or("application/x-www-form-urlencoded")
86
+ }
87
+
88
+ pub fn is_json(&self) -> bool {
89
+ self.content_type_str() == "application/json"
90
+ }
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
+
115
+ pub fn is_html(&self) -> bool {
116
+ self.content_type_str() == "text/html"
117
+ }
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
+
135
+ pub fn process(self, ruby: &Ruby, app_proc: Arc<HeapValue<Proc>>) -> magnus::error::Result<()> {
136
+ let response = self.response.clone();
137
+ let result = app_proc.call::<_, Value>((self,));
138
+ if let Err(err) = result {
139
+ Self::internal_error(ruby, response, err);
140
+ }
141
+ Ok(())
142
+ }
143
+
144
+ pub fn internal_error(ruby: &Ruby, response: ItsiHttpResponse, err: Error) {
145
+ if Self::is_connection_closed_err(ruby, &err) {
146
+ debug!("Connection closed by client");
147
+ response.close();
148
+ } else if let Some(rb_err) = err.value() {
149
+ print_rb_backtrace(rb_err);
150
+ response.internal_server_error(err.to_string());
151
+ } else {
152
+ response.internal_server_error(err.to_string());
153
+ }
154
+ }
155
+
156
+ pub fn error(self, message: String) {
157
+ self.response.internal_server_error(message);
158
+ }
159
+
160
+ pub(crate) async fn process_request(
161
+ app: Arc<HeapValue<Proc>>,
162
+ hyper_request: HttpRequest,
163
+ context: &HttpRequestContext,
164
+ script_name: String,
165
+ nonblocking: bool,
166
+ ) -> itsi_error::Result<HttpResponse> {
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
+ }
195
+ }
196
+ Err(err_resp) => Ok(err_resp),
197
+ }
198
+ }
199
+
200
+ pub(crate) async fn new(
201
+ request: HttpRequest,
202
+ context: &HttpRequestContext,
203
+ script_name: String,
204
+ ) -> Result<(ItsiHttpRequest, mpsc::Receiver<ByteFrame>), HttpResponse> {
205
+ let (parts, body) = request.into_parts();
206
+ let body = if context.server_params.streamable_body {
207
+ ItsiBody::Stream(ItsiBodyProxy::new(body))
208
+ } else {
209
+ let mut body_bytes = BigBytes::new();
210
+ let mut stream = body.into_data_stream();
211
+ while let Some(chunk) = stream.next().await {
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
+ }
222
+ }
223
+ ItsiBody::Buffered(body_bytes)
224
+ };
225
+ let response_channel = mpsc::channel::<ByteFrame>(100);
226
+ Ok((
227
+ Self {
228
+ context: context.clone(),
229
+ version: parts.version,
230
+ response: ItsiHttpResponse::new(parts.clone(), response_channel.0),
231
+ start: Instant::now(),
232
+ script_name,
233
+ body,
234
+ parts,
235
+ },
236
+ response_channel.1,
237
+ ))
238
+ }
239
+
240
+ pub(crate) fn path(&self) -> MagnusResult<&str> {
241
+ Ok(self
242
+ .parts
243
+ .uri
244
+ .path()
245
+ .strip_prefix(self.script_name.trim_end_matches('/'))
246
+ .unwrap_or(self.parts.uri.path()))
247
+ }
248
+
249
+ pub(crate) fn script_name(&self) -> MagnusResult<&str> {
250
+ Ok(&self.script_name)
251
+ }
252
+
253
+ pub(crate) fn query_string(&self) -> MagnusResult<&str> {
254
+ Ok(self.parts.uri.query().unwrap_or(""))
255
+ }
256
+
257
+ pub(crate) fn method(&self) -> MagnusResult<&str> {
258
+ Ok(self.parts.method.as_str())
259
+ }
260
+
261
+ pub(crate) fn version(&self) -> MagnusResult<&str> {
262
+ Ok(match self.version {
263
+ Version::HTTP_09 => "HTTP/0.9",
264
+ Version::HTTP_10 => "HTTP/1.0",
265
+ Version::HTTP_11 => "HTTP/1.1",
266
+ Version::HTTP_2 => "HTTP/2.0",
267
+ Version::HTTP_3 => "HTTP/3.0",
268
+ _ => "HTTP/Unknown",
269
+ })
270
+ }
271
+
272
+ pub(crate) fn rack_protocol(&self) -> MagnusResult<Vec<&str>> {
273
+ Ok(self
274
+ .parts
275
+ .headers
276
+ .get("upgrade")
277
+ .or_else(|| self.parts.headers.get("protocol"))
278
+ .map(|value| {
279
+ value
280
+ .to_str()
281
+ .unwrap_or("")
282
+ .split(',')
283
+ .map(|s| s.trim())
284
+ .collect::<Vec<&str>>()
285
+ })
286
+ .unwrap_or_else(|| vec!["http"]))
287
+ }
288
+
289
+ pub(crate) fn host(&self) -> MagnusResult<&str> {
290
+ Ok(self
291
+ .parts
292
+ .uri
293
+ .host()
294
+ .unwrap_or_else(|| &self.context.listener.host))
295
+ }
296
+
297
+ pub(crate) fn scheme(&self) -> MagnusResult<&str> {
298
+ Ok(self
299
+ .parts
300
+ .uri
301
+ .scheme()
302
+ .map(|scheme| scheme.as_str())
303
+ .unwrap_or_else(|| &self.context.listener.scheme))
304
+ }
305
+
306
+ pub(crate) fn headers(&self) -> MagnusResult<Vec<(&str, &str)>> {
307
+ Ok(self
308
+ .parts
309
+ .headers
310
+ .iter()
311
+ .map(|(hn, hv)| (hn.as_str(), hv.to_str().unwrap_or("")))
312
+ .collect::<Vec<(&str, &str)>>())
313
+ }
314
+
315
+ pub fn header(&self, name: String) -> MagnusResult<Option<Vec<&str>>> {
316
+ let result: Vec<&str> = self
317
+ .parts
318
+ .headers
319
+ .get_all(&name)
320
+ .iter()
321
+ .filter_map(|value| value.to_str().ok())
322
+ .collect();
323
+ Ok(Some(result))
324
+ }
325
+
326
+ pub(crate) fn remote_addr(&self) -> MagnusResult<&str> {
327
+ Ok(&self.context.addr)
328
+ }
329
+
330
+ pub(crate) fn port(&self) -> MagnusResult<u16> {
331
+ Ok(self
332
+ .parts
333
+ .uri
334
+ .port_u16()
335
+ .unwrap_or(self.context.listener.port))
336
+ }
337
+
338
+ pub(crate) fn body(&self) -> MagnusResult<Option<Value>> {
339
+ Ok(self.body.into_value())
340
+ }
341
+
342
+ pub(crate) fn response(&self) -> MagnusResult<ItsiHttpResponse> {
343
+ Ok(self.response.clone())
344
+ }
345
+ }