itsi-server 0.2.15 → 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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +75 -73
  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.lock +1 -1
  7. data/ext/itsi_server/Cargo.toml +3 -1
  8. data/ext/itsi_server/extconf.rb +3 -1
  9. data/ext/itsi_server/src/lib.rs +7 -1
  10. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
  11. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +6 -6
  12. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
  13. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +71 -42
  14. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
  15. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +6 -15
  16. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +32 -6
  17. data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
  18. data/ext/itsi_server/src/server/binds/listener.rs +49 -8
  19. data/ext/itsi_server/src/server/frame_stream.rs +142 -0
  20. data/ext/itsi_server/src/server/http_message_types.rs +143 -10
  21. data/ext/itsi_server/src/server/io_stream.rs +28 -5
  22. data/ext/itsi_server/src/server/lifecycle_event.rs +1 -1
  23. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
  24. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
  25. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
  26. data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
  27. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -58
  28. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +6 -9
  29. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +27 -42
  30. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +65 -14
  31. data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +1 -1
  32. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +8 -11
  33. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +21 -8
  34. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
  35. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +1 -5
  36. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
  37. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +13 -6
  38. data/ext/itsi_server/src/server/mod.rs +1 -0
  39. data/ext/itsi_server/src/server/process_worker.rs +5 -5
  40. data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
  41. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +87 -31
  42. data/ext/itsi_server/src/server/serve_strategy/mod.rs +1 -0
  43. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +166 -206
  44. data/ext/itsi_server/src/server/signal.rs +37 -9
  45. data/ext/itsi_server/src/server/thread_worker.rs +92 -70
  46. data/ext/itsi_server/src/services/itsi_http_service.rs +67 -62
  47. data/ext/itsi_server/src/services/mime_types.rs +185 -183
  48. data/ext/itsi_server/src/services/rate_limiter.rs +16 -34
  49. data/ext/itsi_server/src/services/static_file_server.rs +35 -60
  50. data/lib/itsi/http_request.rb +31 -39
  51. data/lib/itsi/http_response.rb +5 -0
  52. data/lib/itsi/rack_env_pool.rb +59 -0
  53. data/lib/itsi/server/config/config_helpers.rb +1 -2
  54. data/lib/itsi/server/config/dsl.rb +5 -4
  55. data/lib/itsi/server/config/middleware/etag.md +3 -7
  56. data/lib/itsi/server/config/middleware/etag.rb +2 -4
  57. data/lib/itsi/server/config/middleware/proxy.rb +1 -1
  58. data/lib/itsi/server/config/middleware/rackup_file.rb +2 -2
  59. data/lib/itsi/server/config/options/auto_reload_config.rb +6 -2
  60. data/lib/itsi/server/config/options/include.rb +5 -2
  61. data/lib/itsi/server/config/options/listen_backlog.rb +1 -1
  62. data/lib/itsi/server/config/options/pipeline_flush.md +16 -0
  63. data/lib/itsi/server/config/options/pipeline_flush.rb +19 -0
  64. data/lib/itsi/server/config/options/send_buffer_size.md +15 -0
  65. data/lib/itsi/server/config/options/send_buffer_size.rb +19 -0
  66. data/lib/itsi/server/config/options/writev.md +25 -0
  67. data/lib/itsi/server/config/options/writev.rb +19 -0
  68. data/lib/itsi/server/config.rb +43 -31
  69. data/lib/itsi/server/default_config/Itsi.rb +1 -4
  70. data/lib/itsi/server/grpc/grpc_call.rb +2 -0
  71. data/lib/itsi/server/grpc/grpc_interface.rb +2 -2
  72. data/lib/itsi/server/rack/handler/itsi.rb +3 -1
  73. data/lib/itsi/server/rack_interface.rb +17 -12
  74. data/lib/itsi/server/route_tester.rb +1 -1
  75. data/lib/itsi/server/scheduler_interface.rb +2 -0
  76. data/lib/itsi/server/version.rb +1 -1
  77. data/lib/itsi/server.rb +1 -0
  78. data/lib/ruby_lsp/itsi/addon.rb +12 -13
  79. metadata +10 -1
@@ -1,4 +1,9 @@
1
- use bytes::{Buf, Bytes, BytesMut};
1
+ use crate::server::{
2
+ frame_stream::{BufferedStream, FrameStream},
3
+ http_message_types::{HttpBody, HttpResponse},
4
+ serve_strategy::single_mode::RunningPhase,
5
+ };
6
+ use bytes::{Buf, Bytes};
2
7
  use derive_more::Debug;
3
8
  use futures::stream::{unfold, StreamExt};
4
9
  use http::{
@@ -6,10 +11,11 @@ use http::{
6
11
  request::Parts,
7
12
  HeaderMap, HeaderName, HeaderValue, Request, Response, StatusCode,
8
13
  };
9
- use http_body_util::{combinators::BoxBody, Empty, Full, StreamBody};
10
- use hyper::{body::Frame, upgrade::Upgraded};
14
+ use http_body_util::Empty;
15
+ use hyper::upgrade::Upgraded;
11
16
  use hyper_util::rt::TokioIo;
12
17
  use itsi_error::Result;
18
+ use itsi_rb_helpers::call_without_gvl;
13
19
  use itsi_tracing::error;
14
20
  use magnus::error::Result as MagnusResult;
15
21
  use memchr::{memchr, memchr_iter};
@@ -17,105 +23,66 @@ use parking_lot::RwLock;
17
23
  use std::{
18
24
  collections::HashMap,
19
25
  io,
26
+ ops::Deref,
20
27
  os::{fd::FromRawFd, unix::net::UnixStream},
21
28
  str::FromStr,
22
29
  sync::Arc,
30
+ time::Duration,
23
31
  };
24
32
  use tokio::{
25
33
  io::AsyncReadExt,
26
34
  net::UnixStream as TokioUnixStream,
27
- sync::{
28
- mpsc::{self},
29
- watch,
30
- },
35
+ sync::{mpsc::Sender, oneshot::Sender as OneshotSender, watch},
31
36
  };
32
- use tokio_stream::wrappers::ReceiverStream;
33
37
  use tokio_util::io::ReaderStream;
34
- use tracing::warn;
35
-
36
- use crate::server::{
37
- byte_frame::ByteFrame, http_message_types::HttpResponse,
38
- serve_strategy::single_mode::RunningPhase,
39
- };
38
+ use tracing::{info, warn};
40
39
 
41
40
  #[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)]
42
41
  #[derive(Debug, Clone)]
43
42
  pub struct ItsiHttpResponse {
44
- pub data: Arc<ResponseData>,
43
+ pub inner: Arc<ResponseInner>,
44
+ }
45
+
46
+ impl Deref for ItsiHttpResponse {
47
+ type Target = Arc<ResponseInner>;
48
+
49
+ fn deref(&self) -> &Self::Target {
50
+ &self.inner
51
+ }
45
52
  }
46
53
 
47
54
  #[derive(Debug)]
48
- pub struct ResponseData {
55
+ pub struct ResponseInner {
56
+ pub frame_writer: RwLock<Option<Sender<Bytes>>>,
49
57
  pub response: RwLock<Option<HttpResponse>>,
50
- pub response_writer: RwLock<Option<mpsc::Sender<ByteFrame>>>,
51
- pub response_buffer: RwLock<BytesMut>,
52
58
  pub hijacked_socket: RwLock<Option<UnixStream>>,
53
- pub parts: Parts,
59
+ pub response_sender: RwLock<Option<OneshotSender<ResponseFrame>>>,
60
+ pub shutdown_rx: watch::Receiver<RunningPhase>,
61
+ pub parts: Arc<Parts>,
62
+ }
63
+
64
+ #[derive(Debug)]
65
+ pub enum ResponseFrame {
66
+ HttpResponse(HttpResponse),
67
+ HijackedResponse(ItsiHttpResponse),
54
68
  }
55
69
 
56
70
  impl ItsiHttpResponse {
57
- pub async fn build(
58
- &self,
59
- first_frame: ByteFrame,
60
- receiver: mpsc::Receiver<ByteFrame>,
71
+ pub fn new(
72
+ parts: Arc<Parts>,
73
+ response_sender: OneshotSender<ResponseFrame>,
61
74
  shutdown_rx: watch::Receiver<RunningPhase>,
62
- ) -> HttpResponse {
63
- if self.is_hijacked() {
64
- return match self.process_hijacked_response().await {
65
- Ok(result) => result,
66
- Err(e) => {
67
- error!("Error processing hijacked response: {}", e);
68
- Response::new(BoxBody::new(Empty::new()))
69
- }
70
- };
75
+ ) -> Self {
76
+ Self {
77
+ inner: Arc::new(ResponseInner {
78
+ parts,
79
+ shutdown_rx,
80
+ response_sender: RwLock::new(Some(response_sender)),
81
+ frame_writer: RwLock::new(None),
82
+ response: RwLock::new(Some(Response::new(HttpBody::empty()))),
83
+ hijacked_socket: RwLock::new(None),
84
+ }),
71
85
  }
72
- let mut response = self.data.response.write().take().unwrap();
73
- *response.body_mut() = if matches!(first_frame, ByteFrame::Empty) {
74
- BoxBody::new(Empty::new())
75
- } else if matches!(first_frame, ByteFrame::End(_)) {
76
- BoxBody::new(Full::new(first_frame.into()))
77
- } else {
78
- let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame))));
79
- let frame_stream = unfold(
80
- (ReceiverStream::new(receiver), shutdown_rx),
81
- |(mut receiver, mut shutdown_rx)| async move {
82
- if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
83
- return None;
84
- }
85
- loop {
86
- tokio::select! {
87
- maybe_bytes = receiver.next() => {
88
- match maybe_bytes {
89
- Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => {
90
- return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx)));
91
- }
92
- _ => {
93
- return None;
94
- }
95
- }
96
- },
97
- _ = shutdown_rx.changed() => {
98
- match *shutdown_rx.borrow() {
99
- RunningPhase::ShutdownPending => {
100
- warn!("Disconnecting streaming client.");
101
- return None;
102
- },
103
- _ => continue,
104
- }
105
- }
106
- }
107
- }
108
- },
109
- );
110
-
111
- let combined_stream = initial_frame.chain(frame_stream);
112
- BoxBody::new(StreamBody::new(combined_stream))
113
- };
114
- response
115
- }
116
-
117
- pub fn close(&self) {
118
- self.data.response_writer.write().take();
119
86
  }
120
87
 
121
88
  async fn two_way_bridge(upgraded: Upgraded, local: TokioUnixStream) -> io::Result<()> {
@@ -163,8 +130,7 @@ impl ItsiHttpResponse {
163
130
  &self,
164
131
  ) -> Result<(HeaderMap, StatusCode, bool, TokioUnixStream)> {
165
132
  let hijacked_socket =
166
- self.data
167
- .hijacked_socket
133
+ self.hijacked_socket
168
134
  .write()
169
135
  .take()
170
136
  .ok_or(itsi_error::ItsiError::InvalidInput(
@@ -191,9 +157,9 @@ impl ItsiHttpResponse {
191
157
  pub async fn process_hijacked_response(&self) -> Result<HttpResponse> {
192
158
  let (headers, status, requires_upgrade, reader) = self.read_hijacked_headers().await?;
193
159
  let mut response = if requires_upgrade {
194
- let parts = self.data.parts.clone();
160
+ let parts = self.parts.clone();
195
161
  tokio::spawn(async move {
196
- let mut req = Request::from_parts(parts, Empty::<Bytes>::new());
162
+ let mut req = Request::from_parts((*parts).clone(), Empty::<Bytes>::new());
197
163
  match hyper::upgrade::on(&mut req).await {
198
164
  Ok(upgraded) => {
199
165
  Self::two_way_bridge(upgraded, reader)
@@ -203,14 +169,14 @@ impl ItsiHttpResponse {
203
169
  Err(e) => eprintln!("upgrade error: {:?}", e),
204
170
  }
205
171
  });
206
- Response::new(BoxBody::new(Empty::new()))
172
+ Response::new(HttpBody::empty())
207
173
  } else {
208
174
  let stream = ReaderStream::new(reader);
209
175
  let boxed_body = if headers
210
176
  .get(TRANSFER_ENCODING)
211
177
  .is_some_and(|h| h == "chunked")
212
178
  {
213
- BoxBody::new(StreamBody::new(unfold(
179
+ HttpBody::stream(unfold(
214
180
  (stream, Vec::new()),
215
181
  |(mut stream, mut buf)| async move {
216
182
  loop {
@@ -231,7 +197,7 @@ impl ItsiHttpResponse {
231
197
  if buf.starts_with(b"\r\n") {
232
198
  buf.drain(..2);
233
199
  }
234
- return Some((Ok(Frame::data(Bytes::from(data))), (stream, buf)));
200
+ return Some((Ok(Bytes::from(data)), (stream, buf)));
235
201
  }
236
202
  match stream.next().await {
237
203
  Some(Ok(chunk)) => buf.extend_from_slice(&chunk),
@@ -239,15 +205,11 @@ impl ItsiHttpResponse {
239
205
  }
240
206
  }
241
207
  },
242
- )))
208
+ ))
243
209
  } else {
244
- BoxBody::new(StreamBody::new(stream.map(
245
- |result: std::result::Result<Bytes, io::Error>| {
246
- result
247
- .map(Frame::data)
248
- .map_err(|e| unreachable!("unexpected io error: {:?}", e))
249
- },
250
- )))
210
+ HttpBody::stream(stream.map(|result: std::result::Result<Bytes, io::Error>| {
211
+ result.map_err(|e| unreachable!("unexpected io error: {:?}", e))
212
+ }))
251
213
  };
252
214
  Response::new(boxed_body)
253
215
  };
@@ -259,59 +221,97 @@ impl ItsiHttpResponse {
259
221
 
260
222
  pub fn internal_server_error(&self, message: String) {
261
223
  error!(message);
262
- self.data.response_writer.write().take();
263
- if let Some(ref mut response) = *self.data.response.write() {
224
+ self.close_write().ok();
225
+ if let Some(mut response) = self.response.write().take() {
264
226
  *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
227
+ if let Some(sender) = self.response_sender.write().take() {
228
+ sender.send(ResponseFrame::HttpResponse(response)).ok();
229
+ }
265
230
  }
266
231
  }
267
232
 
268
- pub fn send_frame(&self, frame: Bytes) -> MagnusResult<()> {
269
- self.send_frame_into(ByteFrame::Data(frame), &self.data.response_writer)
233
+ pub fn service_unavailable(&self) {
234
+ self.close_write().ok();
235
+ if let Some(mut response) = self.response.write().take() {
236
+ *response.status_mut() = StatusCode::SERVICE_UNAVAILABLE;
237
+ if let Some(sender) = self.response_sender.write().take() {
238
+ sender.send(ResponseFrame::HttpResponse(response)).ok();
239
+ }
240
+ }
270
241
  }
271
242
 
272
- pub fn recv_frame(&self) {
273
- // not implemented
243
+ pub fn send_frame(&self, frame: Bytes) -> MagnusResult<()> {
244
+ {
245
+ if self.frame_writer.read().is_none() && self.response.read().is_some() {
246
+ if let Some(mut response) = self.response.write().take() {
247
+ let (writer, reader) = tokio::sync::mpsc::channel::<Bytes>(256);
248
+ let shutdown_rx = self.shutdown_rx.clone();
249
+ let frame_stream = FrameStream::new(reader, shutdown_rx.clone());
250
+
251
+ let buffered =
252
+ BufferedStream::new(frame_stream, 32 * 1024, Duration::from_millis(10));
253
+ *response.body_mut() = HttpBody::stream(buffered);
254
+ self.frame_writer.write().replace(writer);
255
+ if let Some(sender) = self.response_sender.write().take() {
256
+ sender.send(ResponseFrame::HttpResponse(response)).ok();
257
+ }
258
+ } else {
259
+ info!("No response!");
260
+ }
261
+ }
262
+ }
263
+ if let Some(frame_writer) = self.frame_writer.read().as_ref() {
264
+ call_without_gvl(|| frame_writer.blocking_send(frame))
265
+ .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?;
266
+ }
267
+ Ok(())
274
268
  }
275
269
 
276
- pub fn flush(&self) {
277
- // no-op
270
+ pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<()> {
271
+ if self.frame_writer.read().is_some() {
272
+ self.send_frame(frame)?;
273
+ self.close()?;
274
+ return Ok(());
275
+ }
276
+ if let Some(mut response) = self.response.write().take() {
277
+ if frame.is_empty() {
278
+ *response.body_mut() = HttpBody::empty();
279
+ } else {
280
+ *response.body_mut() = HttpBody::full(frame);
281
+ }
282
+ if let Some(sender) = self.response_sender.write().take() {
283
+ sender.send(ResponseFrame::HttpResponse(response)).ok();
284
+ }
285
+ }
286
+
287
+ Ok(())
278
288
  }
279
289
 
280
- pub fn is_closed(&self) -> bool {
281
- self.data.response_writer.write().is_none()
290
+ pub fn close_write(&self) -> MagnusResult<bool> {
291
+ self.frame_writer.write().take();
292
+ Ok(true)
282
293
  }
283
294
 
284
- pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<()> {
285
- let result = self.send_frame_into(ByteFrame::End(frame), &self.data.response_writer);
286
- self.data.response_writer.write().take();
287
- result
295
+ pub fn recv_frame(&self) {
296
+ // not implemented
288
297
  }
289
298
 
290
- pub fn send_frame_into(
291
- &self,
292
- frame: ByteFrame,
293
- writer: &RwLock<Option<mpsc::Sender<ByteFrame>>>,
294
- ) -> MagnusResult<()> {
295
- if let Some(writer) = writer.write().as_ref() {
296
- return Ok(writer
297
- .blocking_send(frame)
298
- .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?);
299
- }
300
- Ok(())
299
+ pub fn is_closed(&self) -> bool {
300
+ self.response.read().is_none() && self.frame_writer.read().is_none()
301
301
  }
302
302
 
303
303
  pub fn is_hijacked(&self) -> bool {
304
- self.data.hijacked_socket.read().is_some()
304
+ self.hijacked_socket.read().is_some()
305
305
  }
306
306
 
307
- pub fn close_write(&self) -> MagnusResult<bool> {
308
- self.data.response_writer.write().take();
309
- Ok(true)
307
+ pub fn close(&self) -> MagnusResult<()> {
308
+ self.close_write()?;
309
+ self.close_read()?;
310
+ Ok(())
310
311
  }
311
312
 
312
313
  pub fn accept_str(&self) -> &str {
313
- self.data
314
- .parts
314
+ self.parts
315
315
  .headers
316
316
  .get(ACCEPT)
317
317
  .and_then(|hv| hv.to_str().ok()) // handle invalid utf-8
@@ -330,25 +330,9 @@ impl ItsiHttpResponse {
330
330
  Ok(true)
331
331
  }
332
332
 
333
- pub fn new(parts: Parts, response_writer: mpsc::Sender<ByteFrame>) -> Self {
334
- Self {
335
- data: Arc::new(ResponseData {
336
- response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
337
- response_writer: RwLock::new(Some(response_writer)),
338
- response_buffer: RwLock::new(BytesMut::new()),
339
- hijacked_socket: RwLock::new(None),
340
- parts,
341
- }),
342
- }
343
- }
344
-
345
- pub fn add_header(&self, name: Bytes, value: Bytes) -> MagnusResult<()> {
346
- let header_name: HeaderName = HeaderName::from_bytes(&name).map_err(|e| {
347
- itsi_error::ItsiError::InvalidInput(format!("Invalid header name {:?}: {:?}", name, e))
348
- })?;
349
- if let Some(ref mut resp) = *self.data.response.write() {
350
- let headers_mut = resp.headers_mut();
351
- self.insert_header(headers_mut, &header_name, value);
333
+ pub fn reserve_headers(&self, header_count: usize) -> MagnusResult<()> {
334
+ if let Some(ref mut resp) = *self.response.write() {
335
+ resp.headers_mut().try_reserve(header_count).ok();
352
336
  }
353
337
  Ok(())
354
338
  }
@@ -394,8 +378,22 @@ impl ItsiHttpResponse {
394
378
  }
395
379
  }
396
380
 
381
+ pub fn add_header(&self, header_name: Bytes, value: Bytes) -> MagnusResult<()> {
382
+ if let Some(ref mut resp) = *self.response.write() {
383
+ let headers_mut = resp.headers_mut();
384
+ let header_name = HeaderName::from_bytes(&header_name).map_err(|e| {
385
+ itsi_error::ItsiError::InvalidInput(format!(
386
+ "Invalid header name {:?}: {:?}",
387
+ header_name, e
388
+ ))
389
+ })?;
390
+ self.insert_header(headers_mut, &header_name, value);
391
+ }
392
+ Ok(())
393
+ }
394
+
397
395
  pub fn add_headers(&self, headers: HashMap<Bytes, Vec<Bytes>>) -> MagnusResult<()> {
398
- if let Some(ref mut resp) = *self.data.response.write() {
396
+ if let Some(ref mut resp) = *self.response.write() {
399
397
  let headers_mut = resp.headers_mut();
400
398
  for (name, values) in headers {
401
399
  let header_name = HeaderName::from_bytes(&name).map_err(|e| {
@@ -414,7 +412,7 @@ impl ItsiHttpResponse {
414
412
  }
415
413
 
416
414
  pub fn set_status(&self, status: u16) -> MagnusResult<()> {
417
- if let Some(ref mut resp) = *self.data.response.write() {
415
+ if let Some(ref mut resp) = *self.response.write() {
418
416
  *resp.status_mut() = StatusCode::from_u16(status).map_err(|e| {
419
417
  itsi_error::ItsiError::InvalidInput(format!(
420
418
  "Invalid status code {:?}: {:?}",
@@ -428,13 +426,14 @@ impl ItsiHttpResponse {
428
426
  pub fn hijack(&self, fd: i32) -> MagnusResult<()> {
429
427
  let stream = unsafe { UnixStream::from_raw_fd(fd) };
430
428
 
431
- *self.data.hijacked_socket.write() = Some(stream);
432
- if let Some(writer) = self.data.response_writer.write().as_ref() {
433
- writer
434
- .blocking_send(ByteFrame::Empty)
435
- .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?
429
+ *self.hijacked_socket.write() = Some(stream);
430
+ if let Some(sender) = self.response_sender.write().take() {
431
+ sender
432
+ .send(ResponseFrame::HijackedResponse(self.clone()))
433
+ .ok();
436
434
  }
437
- self.close();
435
+
436
+ self.close()?;
438
437
  Ok(())
439
438
  }
440
439
  }
@@ -32,18 +32,8 @@ struct PatternGroup {
32
32
  /// component that contains a wildcard character.
33
33
  fn extract_and_canonicalize_base_dir(pattern: &str) -> PathBuf {
34
34
  if !(pattern.contains("*") || pattern.contains("?") || pattern.contains('[')) {
35
- if let Ok(metadata) = fs::metadata(pattern) {
36
- if metadata.is_dir() {
37
- return fs::canonicalize(pattern).unwrap();
38
- }
39
- if metadata.is_file() {
40
- return fs::canonicalize(pattern)
41
- .unwrap()
42
- .parent()
43
- .unwrap()
44
- .to_path_buf();
45
- }
46
- }
35
+ let base = PathBuf::from(".");
36
+ return fs::canonicalize(&base).unwrap_or(base);
47
37
  }
48
38
 
49
39
  let path = Path::new(pattern);
@@ -63,12 +53,11 @@ fn extract_and_canonicalize_base_dir(pattern: &str) -> PathBuf {
63
53
  base
64
54
  };
65
55
 
66
- // Canonicalize to get the absolute path.
67
56
  fs::canonicalize(&base).unwrap_or(base)
68
57
  }
69
58
 
70
59
  /// Minimum time between triggering the same pattern group (debounce time)
71
- const DEBOUNCE_DURATION: Duration = Duration::from_millis(500);
60
+ const DEBOUNCE_DURATION: Duration = Duration::from_millis(2000);
72
61
 
73
62
  pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<Option<OwnedFd>> {
74
63
  let (r_fd, w_fd): (OwnedFd, OwnedFd) = pipe().map_err(|e| {
@@ -145,13 +134,14 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
145
134
  notify::recommended_watcher(event_fn(sender)).expect("Failed to create watcher");
146
135
  for group in &groups {
147
136
  if watched_dirs.insert(group.base_dir.clone()) {
148
- debug!("Watching {}{}", group.base_dir.display(), group.pattern);
137
+ debug!("Watching {}/{}", group.base_dir.display(), group.pattern);
149
138
  watcher
150
139
  .watch(&group.base_dir, RecursiveMode::Recursive)
151
140
  .expect("Failed to add watch");
152
141
  }
153
142
  }
154
143
 
144
+ debug!("Monitored groups {:?}", groups.len());
155
145
  // Main event loop.
156
146
  for res in rx {
157
147
  match res {
@@ -159,6 +149,7 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
159
149
  if !matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) {
160
150
  continue;
161
151
  }
152
+ debug!("Event fired {:?}", event);
162
153
  let now = Instant::now();
163
154
  for group in &mut groups {
164
155
  for path in event.paths.iter() {
@@ -72,6 +72,12 @@ pub struct ServerParams {
72
72
  pub ruby_thread_request_backlog_size: Option<usize>,
73
73
  pub middleware_loader: HeapValue<Proc>,
74
74
  pub middleware: OnceLock<MiddlewareSet>,
75
+ pub pipeline_flush: bool,
76
+ pub writev: Option<bool>,
77
+ pub max_concurrent_streams: Option<u32>,
78
+ pub max_local_error_reset_streams: Option<usize>,
79
+ pub max_header_list_size: u32,
80
+ pub max_send_buf_size: usize,
75
81
  pub binds: Vec<Bind>,
76
82
  #[debug(skip)]
77
83
  pub(crate) listeners: Mutex<Vec<Listener>>,
@@ -82,7 +88,7 @@ pub struct ServerParams {
82
88
  preexisting_listeners: Option<String>,
83
89
  }
84
90
 
85
- #[derive(Debug, Clone)]
91
+ #[derive(Debug, Clone, Copy)]
86
92
  pub enum ItsiServerTokenPreference {
87
93
  Version,
88
94
  Name,
@@ -109,6 +115,7 @@ pub struct SocketOpts {
109
115
  pub listen_backlog: usize,
110
116
  pub nodelay: bool,
111
117
  pub recv_buffer_size: usize,
118
+ pub send_buffer_size: usize,
112
119
  }
113
120
 
114
121
  impl ServerParams {
@@ -177,19 +184,19 @@ impl ServerParams {
177
184
  }
178
185
 
179
186
  fn from_rb_hash(rb_param_hash: RHash) -> Result<ServerParams> {
187
+ let num_cpus = num_cpus::get_physical() as u8;
180
188
  let workers = rb_param_hash
181
189
  .fetch::<_, Option<u8>>("workers")?
182
- .unwrap_or(num_cpus::get() as u8);
190
+ .unwrap_or(num_cpus);
183
191
  let worker_memory_limit: Option<u64> = rb_param_hash.fetch("worker_memory_limit")?;
184
192
  let silence: bool = rb_param_hash.fetch("silence")?;
185
193
  let multithreaded_reactor: bool = rb_param_hash
186
194
  .fetch::<_, Option<bool>>("multithreaded_reactor")?
187
- .unwrap_or(workers == 1);
195
+ .unwrap_or(workers <= (num_cpus / 3));
188
196
  let pin_worker_cores: bool = rb_param_hash
189
197
  .fetch::<_, Option<bool>>("pin_worker_cores")?
190
- .unwrap_or(true);
198
+ .unwrap_or(false);
191
199
  let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?;
192
-
193
200
  let hooks: Option<RHash> = rb_param_hash.fetch("hooks")?;
194
201
  let hooks = hooks
195
202
  .map(|rhash| -> Result<HashMap<String, HeapValue<Proc>>> {
@@ -248,6 +255,9 @@ impl ServerParams {
248
255
  let recv_buffer_size: usize = rb_param_hash
249
256
  .fetch::<_, Option<usize>>("recv_buffer_size")?
250
257
  .unwrap_or(262_144);
258
+ let send_buffer_size: usize = rb_param_hash
259
+ .fetch::<_, Option<usize>>("send_buffer_size")?
260
+ .unwrap_or(262_144);
251
261
 
252
262
  if let Some(level) = log_level {
253
263
  set_level(&level);
@@ -277,6 +287,14 @@ impl ServerParams {
277
287
  set_target_filters(target_filters);
278
288
  }
279
289
 
290
+ let pipeline_flush: bool = rb_param_hash.fetch("pipeline_flush")?;
291
+ let writev: Option<bool> = rb_param_hash.fetch("writev")?;
292
+ let max_concurrent_streams: Option<u32> = rb_param_hash.fetch("max_concurrent_streams")?;
293
+ let max_local_error_reset_streams: Option<usize> =
294
+ rb_param_hash.fetch("max_local_error_reset_streams")?;
295
+ let max_header_list_size: u32 = rb_param_hash.fetch("max_header_list_size")?;
296
+ let max_send_buf_size: usize = rb_param_hash.fetch("max_send_buf_size")?;
297
+
280
298
  let binds: Option<Vec<String>> = rb_param_hash.fetch("binds")?;
281
299
  let binds = binds
282
300
  .unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
@@ -296,6 +314,7 @@ impl ServerParams {
296
314
  listen_backlog,
297
315
  nodelay,
298
316
  recv_buffer_size,
317
+ send_buffer_size,
299
318
  };
300
319
  let preexisting_listeners = rb_param_hash.delete::<_, Option<String>>("listeners")?;
301
320
 
@@ -317,6 +336,12 @@ impl ServerParams {
317
336
  scheduler_class,
318
337
  ruby_thread_request_backlog_size,
319
338
  oob_gc_responses_threshold,
339
+ pipeline_flush,
340
+ writev,
341
+ max_concurrent_streams,
342
+ max_local_error_reset_streams,
343
+ max_header_list_size,
344
+ max_send_buf_size,
320
345
  binds,
321
346
  itsi_server_token_preference,
322
347
  socket_opts,
@@ -432,7 +457,8 @@ impl ItsiServerConfig {
432
457
  let requires_exec = if !is_single_mode && !server_params.preload {
433
458
  // In cluster mode children are cycled during a reload
434
459
  // and if preload is disabled, will get a clean memory slate,
435
- // so we don't need to exec.
460
+ // so we don't need to exec. We do need to rebind our listeners here.
461
+ server_params.setup_listeners()?;
436
462
  false
437
463
  } else {
438
464
  // In non-cluster mode, or when preloading is enabled, we shouldn't try to
@@ -64,7 +64,7 @@ impl ItsiServer {
64
64
  Ok(if server_config.server_params.read().workers > 1 {
65
65
  ServeStrategy::Cluster(Arc::new(ClusterMode::new(server_config.clone())))
66
66
  } else {
67
- ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone())?))
67
+ ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone(), 0)?))
68
68
  })
69
69
  }
70
70