itsi-server 0.1.1 → 0.1.13

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 (143) 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 +4417 -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 +94 -45
  10. data/ext/itsi_error/Cargo.toml +2 -0
  11. data/ext/itsi_error/src/from.rs +68 -0
  12. data/ext/itsi_error/src/lib.rs +18 -34
  13. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  14. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  15. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  16. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  17. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  18. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  19. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  20. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  21. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  22. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  23. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  24. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  25. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  26. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  27. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  28. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  29. data/ext/itsi_rb_helpers/Cargo.toml +3 -0
  30. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  31. data/ext/itsi_rb_helpers/src/lib.rs +140 -10
  32. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  33. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  34. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  35. 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
  36. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  37. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  38. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  39. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  40. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  41. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  42. data/ext/itsi_scheduler/Cargo.toml +24 -0
  43. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  44. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  45. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  46. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  47. data/ext/itsi_scheduler/src/lib.rs +38 -0
  48. data/ext/itsi_server/Cargo.lock +2956 -0
  49. data/ext/itsi_server/Cargo.toml +73 -13
  50. data/ext/itsi_server/extconf.rb +1 -1
  51. data/ext/itsi_server/src/env.rs +43 -0
  52. data/ext/itsi_server/src/lib.rs +100 -40
  53. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
  54. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
  55. data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
  56. data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
  57. data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
  58. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
  59. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
  60. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  61. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
  62. data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
  63. data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
  64. data/ext/itsi_server/src/server/bind.rs +75 -31
  65. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  66. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  67. data/ext/itsi_server/src/server/cache_store.rs +74 -0
  68. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  69. data/ext/itsi_server/src/server/itsi_service.rs +172 -0
  70. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  71. data/ext/itsi_server/src/server/listener.rs +332 -132
  72. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
  73. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
  74. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
  75. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
  76. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
  77. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
  78. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
  79. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
  80. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
  81. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
  82. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
  83. data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
  84. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
  85. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
  86. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  87. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
  88. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
  89. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
  90. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  91. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
  92. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  97. data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
  98. data/ext/itsi_server/src/server/mod.rs +15 -2
  99. data/ext/itsi_server/src/server/process_worker.rs +229 -0
  100. data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
  101. data/ext/itsi_server/src/server/request_job.rs +11 -0
  102. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
  103. data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
  104. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
  105. data/ext/itsi_server/src/server/signal.rs +93 -0
  106. data/ext/itsi_server/src/server/static_file_server.rs +984 -0
  107. data/ext/itsi_server/src/server/thread_worker.rs +444 -0
  108. data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
  109. data/ext/itsi_server/src/server/tls.rs +187 -60
  110. data/ext/itsi_server/src/server/types.rs +43 -0
  111. data/ext/itsi_tracing/Cargo.toml +5 -0
  112. data/ext/itsi_tracing/src/lib.rs +225 -7
  113. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  114. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  115. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  116. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  117. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  118. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  119. data/lib/itsi/http_request.rb +87 -0
  120. data/lib/itsi/http_response.rb +39 -0
  121. data/lib/itsi/server/Itsi.rb +119 -0
  122. data/lib/itsi/server/config/dsl.rb +506 -0
  123. data/lib/itsi/server/config.rb +131 -0
  124. data/lib/itsi/server/default_app/default_app.rb +38 -0
  125. data/lib/itsi/server/default_app/index.html +91 -0
  126. data/lib/itsi/server/grpc_interface.rb +213 -0
  127. data/lib/itsi/server/rack/handler/itsi.rb +27 -0
  128. data/lib/itsi/server/rack_interface.rb +94 -0
  129. data/lib/itsi/server/scheduler_interface.rb +21 -0
  130. data/lib/itsi/server/scheduler_mode.rb +10 -0
  131. data/lib/itsi/server/signal_trap.rb +29 -0
  132. data/lib/itsi/server/version.rb +1 -1
  133. data/lib/itsi/server.rb +90 -9
  134. data/lib/itsi/standard_headers.rb +86 -0
  135. metadata +122 -31
  136. data/ext/itsi_server/src/request/itsi_request.rs +0 -143
  137. data/ext/itsi_server/src/request/mod.rs +0 -1
  138. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  139. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  140. data/ext/itsi_server/src/server/itsi_server.rs +0 -182
  141. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  142. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
  143. data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,147 @@
1
+ use derive_more::Debug;
2
+ use http::{request::Parts, Response, StatusCode};
3
+ use http_body_util::{combinators::BoxBody, BodyExt, Empty};
4
+ use itsi_error::from::CLIENT_CONNECTION_CLOSED;
5
+ use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
6
+ use itsi_tracing::debug;
7
+ use magnus::{
8
+ block::Proc,
9
+ error::{ErrorType, Result as MagnusResult},
10
+ Error,
11
+ };
12
+ use magnus::{
13
+ value::{LazyId, ReprValue},
14
+ Ruby, Value,
15
+ };
16
+ use std::{sync::Arc, time::Instant};
17
+ use tokio::sync::mpsc::{self};
18
+ use tracing::error;
19
+
20
+ use super::itsi_grpc_stream::ItsiGrpcStream;
21
+ use crate::server::{
22
+ byte_frame::ByteFrame,
23
+ itsi_service::RequestContext,
24
+ request_job::RequestJob,
25
+ types::{HttpRequest, HttpResponse},
26
+ };
27
+
28
+ static ID_MESSAGE: LazyId = LazyId::new("message");
29
+
30
+ #[derive(Debug)]
31
+ #[magnus::wrap(class = "Itsi::GrpcRequest", free_immediately, size)]
32
+ pub struct ItsiGrpcRequest {
33
+ pub parts: Parts,
34
+ pub start: Instant,
35
+ #[debug(skip)]
36
+ pub context: RequestContext,
37
+ #[debug(skip)]
38
+ pub stream: ItsiGrpcStream,
39
+ }
40
+
41
+ impl ItsiGrpcRequest {
42
+ pub fn service_name(&self) -> MagnusResult<String> {
43
+ let path = self.parts.uri.path();
44
+ Ok(path.split('/').nth_back(1).unwrap().to_string())
45
+ }
46
+
47
+ pub fn method_name(&self) -> MagnusResult<String> {
48
+ let path = self.parts.uri.path();
49
+ Ok(path.split('/').nth_back(0).unwrap().to_string())
50
+ }
51
+
52
+ pub fn stream(&self) -> MagnusResult<ItsiGrpcStream> {
53
+ Ok(self.stream.clone())
54
+ }
55
+
56
+ pub fn content_type_str(&self) -> &str {
57
+ self.parts
58
+ .headers
59
+ .get("Content-Type")
60
+ .and_then(|hv| hv.to_str().ok())
61
+ .unwrap_or("application/x-www-form-urlencoded")
62
+ }
63
+
64
+ pub fn is_json(&self) -> bool {
65
+ self.content_type_str() == "application/json"
66
+ }
67
+
68
+ pub fn process(self, ruby: &Ruby, app_proc: Arc<HeapValue<Proc>>) -> magnus::error::Result<()> {
69
+ let response = self.stream.clone();
70
+ let result = app_proc.call::<_, Value>((self,));
71
+ if let Err(err) = result {
72
+ Self::internal_error(ruby, response, err);
73
+ }
74
+ Ok(())
75
+ }
76
+
77
+ pub fn internal_error(_ruby: &Ruby, stream: ItsiGrpcStream, err: Error) {
78
+ if let Some(rb_err) = err.value() {
79
+ print_rb_backtrace(rb_err);
80
+ stream.internal_server_error(err.to_string());
81
+ } else {
82
+ stream.internal_server_error(err.to_string());
83
+ }
84
+ }
85
+
86
+ pub(crate) async fn process_request(
87
+ app: Arc<HeapValue<Proc>>,
88
+ hyper_request: HttpRequest,
89
+ context: &RequestContext,
90
+ ) -> itsi_error::Result<HttpResponse> {
91
+ let (request, mut receiver) = ItsiGrpcRequest::new(hyper_request, context).await;
92
+ let shutdown_channel = context.service.shutdown_channel.clone();
93
+ let response_stream = request.stream.clone();
94
+ match context
95
+ .sender
96
+ .send(RequestJob::ProcessGrpcRequest(request, app))
97
+ .await
98
+ {
99
+ Err(err) => {
100
+ error!("Error occurred: {}", err);
101
+ let mut response = Response::new(BoxBody::new(Empty::new()));
102
+ *response.status_mut() = StatusCode::BAD_REQUEST;
103
+ Ok(response)
104
+ }
105
+ _ => match receiver.recv().await {
106
+ Some(first_frame) => Ok(response_stream
107
+ .build_response(first_frame, receiver, shutdown_channel)
108
+ .await),
109
+ None => Ok(Response::new(BoxBody::new(Empty::new()))),
110
+ },
111
+ }
112
+ }
113
+ pub fn is_connection_closed_err(ruby: &Ruby, err: &Error) -> bool {
114
+ match err.error_type() {
115
+ ErrorType::Jump(_) => false,
116
+ ErrorType::Error(_, _) => false,
117
+ ErrorType::Exception(exception) => {
118
+ exception.is_kind_of(ruby.exception_eof_error())
119
+ && err
120
+ .value()
121
+ .map(|v| {
122
+ v.funcall::<_, _, String>(*ID_MESSAGE, ())
123
+ .unwrap_or("".to_string())
124
+ .eq(CLIENT_CONNECTION_CLOSED)
125
+ })
126
+ .unwrap_or(false)
127
+ }
128
+ }
129
+ }
130
+
131
+ pub(crate) async fn new(
132
+ request: HttpRequest,
133
+ context: &RequestContext,
134
+ ) -> (ItsiGrpcRequest, mpsc::Receiver<ByteFrame>) {
135
+ let (parts, body) = request.into_parts();
136
+ let response_channel = mpsc::channel::<ByteFrame>(100);
137
+ (
138
+ Self {
139
+ context: context.clone(),
140
+ start: Instant::now(),
141
+ parts,
142
+ stream: ItsiGrpcStream::new(response_channel.0, body.into_data_stream()),
143
+ },
144
+ response_channel.1,
145
+ )
146
+ }
147
+ }
@@ -0,0 +1,19 @@
1
+ use derive_more::Debug;
2
+ use http::request::Parts;
3
+ use tokio::sync::mpsc::Sender;
4
+
5
+ use crate::server::byte_frame::ByteFrame;
6
+
7
+ #[derive(Debug, Clone)]
8
+ #[magnus::wrap(class = "Itsi::GrpcResponse", free_immediately, size)]
9
+ pub struct ItsiGrpcResponse {
10
+ pub parts: Parts,
11
+ #[debug(skip)]
12
+ pub sender: Sender<ByteFrame>,
13
+ }
14
+
15
+ impl ItsiGrpcResponse {
16
+ pub fn new(parts: Parts, sender: Sender<ByteFrame>) -> Self {
17
+ Self { parts, sender }
18
+ }
19
+ }
@@ -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
+ }
@@ -0,0 +1,282 @@
1
+ use derive_more::Debug;
2
+ use futures::StreamExt;
3
+ use http::{request::Parts, Response, StatusCode, Version};
4
+ use http_body_util::{combinators::BoxBody, BodyExt, Empty};
5
+ use itsi_error::from::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,
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::server::{
25
+ byte_frame::ByteFrame,
26
+ itsi_service::RequestContext,
27
+ request_job::RequestJob,
28
+ types::{HttpRequest, HttpResponse},
29
+ };
30
+
31
+ static ID_MESSAGE: LazyId = LazyId::new("message");
32
+
33
+ #[derive(Debug)]
34
+ #[magnus::wrap(class = "Itsi::HttpRequest", free_immediately, size)]
35
+ pub struct ItsiHttpRequest {
36
+ pub parts: Parts,
37
+ #[debug(skip)]
38
+ pub body: ItsiBody,
39
+ pub version: Version,
40
+ pub response: ItsiHttpResponse,
41
+ pub start: Instant,
42
+ #[debug(skip)]
43
+ pub context: RequestContext,
44
+ }
45
+
46
+ impl fmt::Display for ItsiHttpRequest {
47
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48
+ write!(
49
+ f,
50
+ "{} {} {}",
51
+ self.version().unwrap(),
52
+ self.method().unwrap(),
53
+ self.path().unwrap()
54
+ )
55
+ }
56
+ }
57
+
58
+ impl ItsiHttpRequest {
59
+ pub fn is_connection_closed_err(ruby: &Ruby, err: &Error) -> bool {
60
+ match err.error_type() {
61
+ ErrorType::Jump(_) => false,
62
+ ErrorType::Error(_, _) => false,
63
+ ErrorType::Exception(exception) => {
64
+ exception.is_kind_of(ruby.exception_eof_error())
65
+ && err
66
+ .value()
67
+ .map(|v| {
68
+ v.funcall::<_, _, String>(*ID_MESSAGE, ())
69
+ .unwrap_or("".to_string())
70
+ .eq(CLIENT_CONNECTION_CLOSED)
71
+ })
72
+ .unwrap_or(false)
73
+ }
74
+ }
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
+ }
87
+
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<()> {
93
+ let response = self.response.clone();
94
+ let result = app_proc.call::<_, Value>((self,));
95
+ if let Err(err) = result {
96
+ Self::internal_error(ruby, response, err);
97
+ }
98
+ Ok(())
99
+ }
100
+
101
+ pub fn internal_error(ruby: &Ruby, response: ItsiHttpResponse, err: Error) {
102
+ if Self::is_connection_closed_err(ruby, &err) {
103
+ debug!("Connection closed by client");
104
+ response.close();
105
+ } else if let Some(rb_err) = err.value() {
106
+ print_rb_backtrace(rb_err);
107
+ response.internal_server_error(err.to_string());
108
+ } else {
109
+ response.internal_server_error(err.to_string());
110
+ }
111
+ }
112
+
113
+ pub fn error(self, message: String) {
114
+ self.response.internal_server_error(message);
115
+ }
116
+
117
+ pub(crate) async fn process_request(
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();
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)
135
+ }
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
+ },
144
+ }
145
+ }
146
+
147
+ pub(crate) async fn new(
148
+ request: HttpRequest,
149
+ context: &RequestContext,
150
+ ) -> (ItsiHttpRequest, mpsc::Receiver<ByteFrame>) {
151
+ let (parts, body) = request.into_parts();
152
+ let body = if context.server_params.streamable_body {
153
+ ItsiBody::Stream(ItsiBodyProxy::new(body))
154
+ } else {
155
+ let mut body_bytes = BigBytes::new();
156
+ let mut stream = body.into_data_stream();
157
+ while let Some(chunk) = stream.next().await {
158
+ let byte_array = chunk.unwrap().to_vec();
159
+ body_bytes.write_all(&byte_array).unwrap();
160
+ }
161
+ ItsiBody::Buffered(body_bytes)
162
+ };
163
+ let response_channel = mpsc::channel::<ByteFrame>(100);
164
+ (
165
+ Self {
166
+ context: context.clone(),
167
+ version: parts.version,
168
+ response: ItsiHttpResponse::new(parts.clone(), response_channel.0),
169
+ start: Instant::now(),
170
+ body,
171
+ parts,
172
+ },
173
+ response_channel.1,
174
+ )
175
+ }
176
+
177
+ pub(crate) fn path(&self) -> MagnusResult<&str> {
178
+ Ok(self
179
+ .parts
180
+ .uri
181
+ .path()
182
+ .strip_prefix(&self.context.server_params.script_name)
183
+ .unwrap_or(self.parts.uri.path()))
184
+ }
185
+
186
+ pub(crate) fn script_name(&self) -> MagnusResult<&str> {
187
+ Ok(&self.context.server_params.script_name)
188
+ }
189
+
190
+ pub(crate) fn query_string(&self) -> MagnusResult<&str> {
191
+ Ok(self.parts.uri.query().unwrap_or(""))
192
+ }
193
+
194
+ pub(crate) fn method(&self) -> MagnusResult<&str> {
195
+ Ok(self.parts.method.as_str())
196
+ }
197
+
198
+ pub(crate) fn version(&self) -> MagnusResult<&str> {
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
+ })
207
+ }
208
+
209
+ pub(crate) fn rack_protocol(&self) -> MagnusResult<Vec<&str>> {
210
+ Ok(self
211
+ .parts
212
+ .headers
213
+ .get("upgrade")
214
+ .or_else(|| self.parts.headers.get("protocol"))
215
+ .map(|value| {
216
+ value
217
+ .to_str()
218
+ .unwrap_or("")
219
+ .split(',')
220
+ .map(|s| s.trim())
221
+ .collect::<Vec<&str>>()
222
+ })
223
+ .unwrap_or_else(|| vec!["http"]))
224
+ }
225
+
226
+ pub(crate) fn host(&self) -> MagnusResult<&str> {
227
+ Ok(self
228
+ .parts
229
+ .uri
230
+ .host()
231
+ .unwrap_or_else(|| &self.context.listener.host))
232
+ }
233
+
234
+ pub(crate) fn scheme(&self) -> MagnusResult<&str> {
235
+ Ok(self
236
+ .parts
237
+ .uri
238
+ .scheme()
239
+ .map(|scheme| scheme.as_str())
240
+ .unwrap_or_else(|| &self.context.listener.scheme))
241
+ }
242
+
243
+ pub(crate) fn headers(&self) -> MagnusResult<Vec<(&str, &str)>> {
244
+ Ok(self
245
+ .parts
246
+ .headers
247
+ .iter()
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))
261
+ }
262
+
263
+ pub(crate) fn remote_addr(&self) -> MagnusResult<&str> {
264
+ Ok(&self.context.addr)
265
+ }
266
+
267
+ pub(crate) fn port(&self) -> MagnusResult<u16> {
268
+ Ok(self
269
+ .parts
270
+ .uri
271
+ .port_u16()
272
+ .unwrap_or(self.context.listener.port))
273
+ }
274
+
275
+ pub(crate) fn body(&self) -> MagnusResult<Option<Value>> {
276
+ Ok(self.body.into_value())
277
+ }
278
+
279
+ pub(crate) fn response(&self) -> MagnusResult<ItsiHttpResponse> {
280
+ Ok(self.response.clone())
281
+ }
282
+ }