itsi-server 0.2.16 → 0.2.17

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +3 -1
  3. data/exe/itsi +6 -1
  4. data/ext/itsi_acme/Cargo.toml +1 -1
  5. data/ext/itsi_scheduler/Cargo.toml +1 -1
  6. data/ext/itsi_server/Cargo.toml +3 -1
  7. data/ext/itsi_server/src/lib.rs +6 -1
  8. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
  9. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +4 -4
  10. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
  11. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +64 -33
  12. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
  13. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +6 -15
  14. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +26 -5
  15. data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
  16. data/ext/itsi_server/src/server/binds/listener.rs +45 -7
  17. data/ext/itsi_server/src/server/frame_stream.rs +142 -0
  18. data/ext/itsi_server/src/server/http_message_types.rs +142 -9
  19. data/ext/itsi_server/src/server/io_stream.rs +28 -5
  20. data/ext/itsi_server/src/server/lifecycle_event.rs +1 -1
  21. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
  22. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
  23. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
  24. data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
  25. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -56
  26. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +5 -7
  27. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +5 -5
  28. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +7 -10
  29. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
  30. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
  31. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +4 -6
  32. data/ext/itsi_server/src/server/mod.rs +1 -0
  33. data/ext/itsi_server/src/server/process_worker.rs +3 -4
  34. data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +16 -12
  35. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +87 -31
  36. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +158 -142
  37. data/ext/itsi_server/src/server/signal.rs +37 -9
  38. data/ext/itsi_server/src/server/thread_worker.rs +84 -69
  39. data/ext/itsi_server/src/services/itsi_http_service.rs +43 -43
  40. data/ext/itsi_server/src/services/static_file_server.rs +28 -47
  41. data/lib/itsi/http_request.rb +31 -39
  42. data/lib/itsi/http_response.rb +5 -0
  43. data/lib/itsi/rack_env_pool.rb +59 -0
  44. data/lib/itsi/server/config/dsl.rb +5 -4
  45. data/lib/itsi/server/config/middleware/proxy.rb +1 -1
  46. data/lib/itsi/server/config/middleware/rackup_file.rb +2 -2
  47. data/lib/itsi/server/config/options/auto_reload_config.rb +6 -2
  48. data/lib/itsi/server/config/options/include.rb +5 -2
  49. data/lib/itsi/server/config/options/pipeline_flush.md +16 -0
  50. data/lib/itsi/server/config/options/pipeline_flush.rb +19 -0
  51. data/lib/itsi/server/config/options/writev.md +25 -0
  52. data/lib/itsi/server/config/options/writev.rb +19 -0
  53. data/lib/itsi/server/config.rb +21 -8
  54. data/lib/itsi/server/default_config/Itsi.rb +1 -4
  55. data/lib/itsi/server/grpc/grpc_call.rb +2 -0
  56. data/lib/itsi/server/grpc/grpc_interface.rb +2 -2
  57. data/lib/itsi/server/rack/handler/itsi.rb +3 -1
  58. data/lib/itsi/server/rack_interface.rb +17 -12
  59. data/lib/itsi/server/scheduler_interface.rb +2 -0
  60. data/lib/itsi/server/version.rb +1 -1
  61. data/lib/itsi/server.rb +1 -0
  62. data/lib/ruby_lsp/itsi/addon.rb +12 -13
  63. metadata +7 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dacff69074fbea9c3b26f533549c9f1b8cf2bb369f8ef15346180fbfac81b4e6
4
- data.tar.gz: cd0d38e8ac1be9114281d7a28ab1ea5d6d9394dff87b7a17c7fb259eee0a1dbe
3
+ metadata.gz: b8c75e7602b936f8ca0140844efef4617149bcd9228826e93cecec3673cba14e
4
+ data.tar.gz: ec9f4ba2c83ab3a0e6683ca7cce2e029f0c62224db9ba3a058caffc30be01504
5
5
  SHA512:
6
- metadata.gz: 5464271b5aa30ba1c02addbd365fc1a7891b357dbb2ef4c02f5f9b26048714c77011ba60a2a82abc2da843bb3e7dbe253105b4ae0e49eeea12b028a2cf6f0cef
7
- data.tar.gz: 88ecd2101bfc629d0d198c14877f9dfdc6cb104a6978712f2f16c3dfb3cc00fa4aeb4cf4e164884af90e175c879b501565c973549c8189e9e5a5a991df1e87bb
6
+ metadata.gz: 9d2f60a69e5cf77986802f15c6168b27a74525fbfa2a8470e9fc28ab3e4f9ff7481ec372099298edb474ab9e7e5da555e6b295422ba3b2e2529cb83ff6a38ff4
7
+ data.tar.gz: 8c833ed1c2da13fd48b5895d334bd548bd651218ec9be952190f45b2b455745240276e090e5a5a621155e73cb8e39a9859437412af04741d7d8cb357e2a0da28
data/Cargo.lock CHANGED
@@ -1644,7 +1644,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1644
1644
 
1645
1645
  [[package]]
1646
1646
  name = "itsi-server"
1647
- version = "0.2.16"
1647
+ version = "0.2.17"
1648
1648
  dependencies = [
1649
1649
  "argon2",
1650
1650
  "async-channel",
@@ -1660,6 +1660,7 @@ dependencies = [
1660
1660
  "either",
1661
1661
  "fs2",
1662
1662
  "futures",
1663
+ "futures-util",
1663
1664
  "globset",
1664
1665
  "http 1.3.1",
1665
1666
  "http-body-util",
@@ -1695,6 +1696,7 @@ dependencies = [
1695
1696
  "serde_magnus",
1696
1697
  "sha-crypt",
1697
1698
  "sha2",
1699
+ "smallvec",
1698
1700
  "socket2",
1699
1701
  "sysinfo",
1700
1702
  "tempfile",
data/exe/itsi CHANGED
@@ -164,7 +164,12 @@ if ENV['COMP_LINE'] || ARGV.include?('--completion')
164
164
  exit
165
165
  end
166
166
 
167
- parser.parse!
167
+ begin
168
+ parser.parse!
169
+ rescue StandardError => e
170
+ puts e.message
171
+ exit
172
+ end
168
173
 
169
174
  case (command = ARGV.shift)
170
175
  when *COMMANDS.keys
@@ -2,7 +2,7 @@
2
2
  name = "itsi_acme"
3
3
  version = "0.1.0"
4
4
  authors = [
5
- "wouterkem <wc@pico.net.nz>",
5
+ "wouterken <wc@pico.net.nz>",
6
6
  "dignifiedquire <me@dignifiedquire.com>",
7
7
  "Florian Uekermann <florian@uekermann.me>",
8
8
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-scheduler"
3
- version = "0.2.16"
3
+ version = "0.2.17"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-server"
3
- version = "0.2.16"
3
+ version = "0.2.17"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -90,3 +90,5 @@ argon2 = "0.5.3"
90
90
  core_affinity = "0.8.3"
91
91
  memchr = "2.7.4"
92
92
  quick_cache = "0.6.13"
93
+ smallvec = "1.15.0"
94
+ futures-util = "0.3.31"
@@ -58,6 +58,7 @@ fn init(ruby: &Ruby) -> Result<()> {
58
58
  request.define_method("rack_protocol", method!(ItsiHttpRequest::rack_protocol, 0))?;
59
59
  request.define_method("host", method!(ItsiHttpRequest::host, 0))?;
60
60
  request.define_method("headers", method!(ItsiHttpRequest::headers, 0))?;
61
+ request.define_method("each_header", method!(ItsiHttpRequest::each_header, 0))?;
61
62
  request.define_method("uri", method!(ItsiHttpRequest::uri, 0))?;
62
63
  request.define_method("header", method!(ItsiHttpRequest::header, 1))?;
63
64
  request.define_method("[]", method!(ItsiHttpRequest::header, 1))?;
@@ -71,6 +72,7 @@ fn init(ruby: &Ruby) -> Result<()> {
71
72
  request.define_method("url_encoded?", method!(ItsiHttpRequest::is_url_encoded, 0))?;
72
73
  request.define_method("multipart?", method!(ItsiHttpRequest::is_multipart, 0))?;
73
74
  request.define_method("url_params", method!(ItsiHttpRequest::url_params, 0))?;
75
+ request.define_method("server_error", method!(ItsiHttpRequest::error, 1))?;
74
76
 
75
77
  let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY);
76
78
  body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?;
@@ -80,6 +82,10 @@ fn init(ruby: &Ruby) -> Result<()> {
80
82
 
81
83
  let response = ruby.get_inner(&ITSI_RESPONSE);
82
84
  response.define_method("[]=", method!(ItsiHttpResponse::add_header, 2))?;
85
+ response.define_method(
86
+ "reserve_headers",
87
+ method!(ItsiHttpResponse::reserve_headers, 1),
88
+ )?;
83
89
  response.define_method("add_header", method!(ItsiHttpResponse::add_header, 2))?;
84
90
  response.define_method("add_headers", method!(ItsiHttpResponse::add_headers, 1))?;
85
91
  response.define_method("status=", method!(ItsiHttpResponse::set_status, 1))?;
@@ -87,7 +93,6 @@ fn init(ruby: &Ruby) -> Result<()> {
87
93
  response.define_method("<<", method!(ItsiHttpResponse::send_frame, 1))?;
88
94
  response.define_method("write", method!(ItsiHttpResponse::send_frame, 1))?;
89
95
  response.define_method("read", method!(ItsiHttpResponse::recv_frame, 0))?;
90
- response.define_method("flush", method!(ItsiHttpResponse::flush, 0))?;
91
96
  response.define_method("closed?", method!(ItsiHttpResponse::is_closed, 0))?;
92
97
  response.define_method(
93
98
  "send_and_close",
@@ -25,6 +25,7 @@ pub struct ItsiBodyProxy {
25
25
  pub enum ItsiBody {
26
26
  Buffered(BigBytes),
27
27
  Stream(ItsiBodyProxy),
28
+ Empty,
28
29
  }
29
30
 
30
31
  impl ItsiBody {
@@ -32,6 +33,7 @@ impl ItsiBody {
32
33
  match self {
33
34
  ItsiBody::Buffered(bytes) => bytes.as_value(),
34
35
  ItsiBody::Stream(proxy) => Some(proxy.clone().into_value()),
36
+ ItsiBody::Empty => None,
35
37
  }
36
38
  }
37
39
  }
@@ -1,6 +1,6 @@
1
1
  use super::itsi_grpc_response_stream::ItsiGrpcResponseStream;
2
2
  use crate::prelude::*;
3
- use crate::server::http_message_types::{HttpRequest, HttpResponse};
3
+ use crate::server::http_message_types::{HttpBody, HttpRequest, HttpResponse};
4
4
  use crate::server::{byte_frame::ByteFrame, request_job::RequestJob};
5
5
  use crate::services::itsi_http_service::HttpRequestContext;
6
6
  use async_compression::futures::bufread::{GzipDecoder, GzipEncoder, ZlibDecoder, ZlibEncoder};
@@ -8,7 +8,7 @@ use bytes::Bytes;
8
8
  use derive_more::Debug;
9
9
  use futures::{executor::block_on, io::Cursor, AsyncReadExt};
10
10
  use http::{request::Parts, Response, StatusCode};
11
- use http_body_util::{combinators::BoxBody, BodyExt, Empty};
11
+ use http_body_util::BodyExt;
12
12
  use itsi_error::CLIENT_CONNECTION_CLOSED;
13
13
  use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
14
14
  use itsi_tracing::debug;
@@ -139,7 +139,7 @@ impl ItsiGrpcCall {
139
139
  {
140
140
  Err(err) => {
141
141
  error!("Error occurred: {}", err);
142
- let mut response = Response::new(BoxBody::new(Empty::new()));
142
+ let mut response = Response::new(HttpBody::empty());
143
143
  *response.status_mut() = StatusCode::BAD_REQUEST;
144
144
  Ok(response)
145
145
  }
@@ -147,7 +147,7 @@ impl ItsiGrpcCall {
147
147
  Some(first_frame) => Ok(response_stream
148
148
  .build_response(first_frame, receiver, shutdown_channel)
149
149
  .await),
150
- None => Ok(Response::new(BoxBody::new(Empty::new()))),
150
+ None => Ok(Response::new(HttpBody::empty())),
151
151
  },
152
152
  }
153
153
  }
@@ -1,6 +1,6 @@
1
1
  use super::itsi_grpc_call::CompressionAlgorithm;
2
2
  use crate::prelude::*;
3
- use crate::server::http_message_types::HttpResponse;
3
+ use crate::server::http_message_types::{HttpBody, HttpResponse};
4
4
  use crate::server::size_limited_incoming::SizeLimitedIncoming;
5
5
  use crate::server::{byte_frame::ByteFrame, serve_strategy::single_mode::RunningPhase};
6
6
  use bytes::Bytes;
@@ -11,8 +11,8 @@ use http::{
11
11
  header::{HeaderName, HeaderValue},
12
12
  HeaderMap, Response,
13
13
  };
14
- use http_body_util::{combinators::BoxBody, BodyDataStream, BodyExt, Empty, Full, StreamBody};
15
- use hyper::body::{Frame, Incoming};
14
+ use http_body_util::BodyDataStream;
15
+ use hyper::body::Incoming;
16
16
  use magnus::error::Result as MagnusResult;
17
17
  use nix::unistd::pipe;
18
18
  use parking_lot::Mutex;
@@ -161,7 +161,7 @@ impl ItsiGrpcResponseStream {
161
161
  response_headers,
162
162
  incoming_reader: Some(pipe_read),
163
163
  response_sender,
164
- response: Some(Response::new(BoxBody::new(Empty::new()))),
164
+ response: Some(Response::new(HttpBody::empty())),
165
165
  trailer_tx,
166
166
  trailer_rx: Some(trailer_rx),
167
167
  })),
@@ -207,12 +207,12 @@ impl ItsiGrpcResponseStream {
207
207
  let rx = self.inner.lock().trailer_rx.take().unwrap();
208
208
  *response.version_mut() = Version::HTTP_2;
209
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())
210
+ let body_with_trailers = if matches!(first_frame, ByteFrame::Empty) {
211
+ HttpBody::empty()
212
212
  } else if matches!(first_frame, ByteFrame::End(_)) {
213
- BoxBody::new(Full::new(first_frame.into()))
213
+ HttpBody::full(first_frame.into())
214
214
  } else {
215
- let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame))));
215
+ let initial_frame = tokio_stream::once(Ok(Bytes::from(first_frame)));
216
216
  let frame_stream = unfold(
217
217
  (ReceiverStream::new(receiver), shutdown_rx),
218
218
  |(mut receiver, mut shutdown_rx)| async move {
@@ -224,7 +224,7 @@ impl ItsiGrpcResponseStream {
224
224
  maybe_bytes = receiver.next() => {
225
225
  match maybe_bytes {
226
226
  Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => {
227
- return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx)));
227
+ return Some((Ok(bytes), (receiver, shutdown_rx)));
228
228
  }
229
229
  _ => {
230
230
  return None;
@@ -234,7 +234,7 @@ impl ItsiGrpcResponseStream {
234
234
  _ = shutdown_rx.changed() => {
235
235
  match *shutdown_rx.borrow() {
236
236
  RunningPhase::ShutdownPending => {
237
- warn!("Disconnecting streaming client.");
237
+ debug!("Disconnecting streaming client.");
238
238
  return None;
239
239
  },
240
240
  _ => continue,
@@ -246,15 +246,16 @@ impl ItsiGrpcResponseStream {
246
246
  );
247
247
 
248
248
  let combined_stream = initial_frame.chain(frame_stream);
249
- BoxBody::new(StreamBody::new(combined_stream))
249
+ HttpBody::stream(combined_stream)
250
250
  }
251
251
  .with_trailers(async move {
252
252
  match rx.await {
253
253
  Ok(trailers) => Some(Ok(trailers)),
254
254
  Err(_err) => None,
255
255
  }
256
- })
257
- .boxed();
256
+ });
257
+
258
+ *response.body_mut() = body_with_trailers;
258
259
  response
259
260
  }
260
261
 
@@ -1,31 +1,30 @@
1
1
  use derive_more::Debug;
2
2
  use futures::StreamExt;
3
- use http::{header::CONTENT_LENGTH, request::Parts, Response, StatusCode, Version};
4
- use http_body_util::{combinators::BoxBody, BodyExt, Empty};
3
+ use http::{header::CONTENT_LENGTH, request::Parts, HeaderValue, Response, StatusCode, Version};
4
+ use http_body_util::BodyExt;
5
5
  use itsi_error::CLIENT_CONNECTION_CLOSED;
6
- use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
7
- use itsi_tracing::{debug, error};
6
+ use itsi_rb_helpers::{funcall_no_ret, print_rb_backtrace, HeapValue};
7
+ use itsi_tracing::debug;
8
8
  use magnus::{
9
- block::Proc,
9
+ block::{yield_values, Proc},
10
10
  error::{ErrorType, Result as MagnusResult},
11
- Error, RHash, Symbol,
11
+ Error, IntoValue, RHash, Symbol,
12
12
  };
13
13
  use magnus::{
14
14
  value::{LazyId, ReprValue},
15
15
  Ruby, Value,
16
16
  };
17
17
  use std::{fmt, io::Write, sync::Arc, time::Instant};
18
- use tokio::sync::mpsc::{self};
18
+ use tracing::error;
19
19
 
20
20
  use super::{
21
21
  itsi_body_proxy::{big_bytes::BigBytes, ItsiBody, ItsiBodyProxy},
22
- itsi_http_response::ItsiHttpResponse,
22
+ itsi_http_response::{ItsiHttpResponse, ResponseFrame},
23
23
  };
24
24
  use crate::{
25
25
  default_responses::{INTERNAL_SERVER_ERROR_RESPONSE, SERVICE_UNAVAILABLE_RESPONSE},
26
26
  server::{
27
- byte_frame::ByteFrame,
28
- http_message_types::{HttpRequest, HttpResponse},
27
+ http_message_types::{HttpBody, HttpRequest, HttpResponse},
29
28
  request_job::RequestJob,
30
29
  size_limited_incoming::MaxBodySizeReached,
31
30
  },
@@ -33,11 +32,13 @@ use crate::{
33
32
  };
34
33
 
35
34
  static ID_MESSAGE: LazyId = LazyId::new("message");
35
+ static ID_CALL: LazyId = LazyId::new("call");
36
+ static ZERO_HEADER_VALUE: HeaderValue = HeaderValue::from_static("0");
36
37
 
37
38
  #[derive(Debug)]
38
39
  #[magnus::wrap(class = "Itsi::HttpRequest", free_immediately, size)]
39
40
  pub struct ItsiHttpRequest {
40
- pub parts: Parts,
41
+ pub parts: Arc<Parts>,
41
42
  #[debug(skip)]
42
43
  pub body: ItsiBody,
43
44
  pub version: Version,
@@ -148,8 +149,10 @@ impl ItsiHttpRequest {
148
149
 
149
150
  pub fn process(self, ruby: &Ruby, app_proc: Arc<HeapValue<Proc>>) -> magnus::error::Result<()> {
150
151
  let response = self.response.clone();
151
- let result = app_proc.call::<_, Value>((self,));
152
- if let Err(err) = result {
152
+
153
+ if let Err(err) =
154
+ funcall_no_ret(app_proc.as_value(), *ID_CALL, [self.into_value_with(ruby)])
155
+ {
153
156
  Self::internal_error(ruby, response, err);
154
157
  }
155
158
  Ok(())
@@ -158,7 +161,7 @@ impl ItsiHttpRequest {
158
161
  pub fn internal_error(ruby: &Ruby, response: ItsiHttpResponse, err: Error) {
159
162
  if Self::is_connection_closed_err(ruby, &err) {
160
163
  debug!("Connection closed by client");
161
- response.close();
164
+ response.close().ok();
162
165
  } else if let Some(rb_err) = err.value() {
163
166
  print_rb_backtrace(rb_err);
164
167
  response.internal_server_error(err.to_string());
@@ -167,7 +170,7 @@ impl ItsiHttpRequest {
167
170
  }
168
171
  }
169
172
 
170
- pub fn error(self, message: String) {
173
+ pub fn error(&self, message: String) {
171
174
  self.response.internal_server_error(message);
172
175
  }
173
176
 
@@ -179,9 +182,7 @@ impl ItsiHttpRequest {
179
182
  nonblocking: bool,
180
183
  ) -> itsi_error::Result<HttpResponse> {
181
184
  match ItsiHttpRequest::new(hyper_request, context, script_name).await {
182
- Ok((request, mut receiver)) => {
183
- let shutdown_channel = context.service.shutdown_receiver.clone();
184
- let response = request.response.clone();
185
+ Ok((request, receiver)) => {
185
186
  let sender = if nonblocking {
186
187
  &context.nonblocking_sender
187
188
  } else {
@@ -192,20 +193,30 @@ impl ItsiHttpRequest {
192
193
  async_channel::TrySendError::Full(_) => Ok(SERVICE_UNAVAILABLE_RESPONSE
193
194
  .to_http_response(context.accept)
194
195
  .await),
195
- async_channel::TrySendError::Closed(err) => {
196
- error!("Error occurred: {:?}", err);
196
+ async_channel::TrySendError::Closed(_) => {
197
+ error!("Channel closed while sending request job");
197
198
  Ok(INTERNAL_SERVER_ERROR_RESPONSE
198
199
  .to_http_response(context.accept)
199
200
  .await)
200
201
  }
201
202
  },
202
- _ => match receiver.recv().await {
203
- Some(first_frame) => Ok(response
204
- .build(first_frame, receiver, shutdown_channel)
205
- .await),
206
- None => Ok(response
207
- .build(ByteFrame::Empty, receiver, shutdown_channel)
208
- .await),
203
+ Ok(_) => match receiver.await {
204
+ Ok(ResponseFrame::HttpResponse(response)) => Ok(response),
205
+ Ok(ResponseFrame::HijackedResponse(response)) => {
206
+ match response.process_hijacked_response().await {
207
+ Ok(result) => Ok(result),
208
+ Err(e) => {
209
+ error!("Error processing hijacked response: {}", e);
210
+ Ok(Response::new(HttpBody::empty()))
211
+ }
212
+ }
213
+ }
214
+ Err(_) => {
215
+ error!("Failed to receive response from receiver");
216
+ Ok(INTERNAL_SERVER_ERROR_RESPONSE
217
+ .to_http_response(context.accept)
218
+ .await)
219
+ }
209
220
  },
210
221
  }
211
222
  }
@@ -217,9 +228,18 @@ impl ItsiHttpRequest {
217
228
  request: HttpRequest,
218
229
  context: &HttpRequestContext,
219
230
  script_name: String,
220
- ) -> Result<(ItsiHttpRequest, mpsc::Receiver<ByteFrame>), HttpResponse> {
231
+ ) -> Result<
232
+ (
233
+ ItsiHttpRequest,
234
+ tokio::sync::oneshot::Receiver<ResponseFrame>,
235
+ ),
236
+ HttpResponse,
237
+ > {
221
238
  let (parts, body) = request.into_parts();
222
- let body = if context.server_params.streamable_body {
239
+ let parts = Arc::new(parts);
240
+ let body = if parts.headers.get(CONTENT_LENGTH) == Some(&ZERO_HEADER_VALUE) {
241
+ ItsiBody::Empty
242
+ } else if context.server_params.streamable_body {
223
243
  ItsiBody::Stream(ItsiBodyProxy::new(body))
224
244
  } else {
225
245
  let mut body_bytes = BigBytes::new();
@@ -228,7 +248,7 @@ impl ItsiHttpRequest {
228
248
  match chunk {
229
249
  Ok(byte_array) => body_bytes.write_all(&byte_array).unwrap(),
230
250
  Err(e) => {
231
- let mut err_resp = Response::new(BoxBody::new(Empty::new()));
251
+ let mut err_resp = Response::new(HttpBody::empty());
232
252
  if e.downcast_ref::<MaxBodySizeReached>().is_some() {
233
253
  *err_resp.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
234
254
  }
@@ -238,18 +258,22 @@ impl ItsiHttpRequest {
238
258
  }
239
259
  ItsiBody::Buffered(body_bytes)
240
260
  };
241
- let response_channel = mpsc::channel::<ByteFrame>(100);
261
+ let (sender, receiver) = tokio::sync::oneshot::channel::<ResponseFrame>();
242
262
  Ok((
243
263
  Self {
244
264
  context: context.clone(),
245
265
  version: parts.version,
246
- response: ItsiHttpResponse::new(parts.clone(), response_channel.0),
266
+ response: ItsiHttpResponse::new(
267
+ parts.clone(),
268
+ sender,
269
+ context.service.shutdown_receiver.clone(),
270
+ ),
247
271
  start: Instant::now(),
248
272
  script_name,
249
273
  body,
250
274
  parts,
251
275
  },
252
- response_channel.1,
276
+ receiver,
253
277
  ))
254
278
  }
255
279
 
@@ -328,6 +352,13 @@ impl ItsiHttpRequest {
328
352
  .collect::<Vec<(&str, &str)>>())
329
353
  }
330
354
 
355
+ pub(crate) fn each_header(&self) -> MagnusResult<()> {
356
+ self.parts.headers.iter().for_each(|(hn, hv)| {
357
+ yield_values::<_, Value>((hn.as_str(), hv.to_str().unwrap_or(""))).ok();
358
+ });
359
+ Ok(())
360
+ }
361
+
331
362
  pub(crate) fn uri(&self) -> MagnusResult<String> {
332
363
  Ok(self.parts.uri.to_string())
333
364
  }