itsi-scheduler 0.2.16 → 0.2.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1 -1
  3. data/ext/itsi_acme/Cargo.toml +1 -1
  4. data/ext/itsi_scheduler/Cargo.toml +1 -1
  5. data/ext/itsi_server/Cargo.toml +3 -1
  6. data/ext/itsi_server/src/lib.rs +6 -1
  7. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
  8. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +4 -4
  9. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
  10. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +64 -33
  11. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
  12. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +422 -110
  13. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +62 -15
  14. data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
  15. data/ext/itsi_server/src/server/binds/listener.rs +45 -7
  16. data/ext/itsi_server/src/server/frame_stream.rs +142 -0
  17. data/ext/itsi_server/src/server/http_message_types.rs +142 -9
  18. data/ext/itsi_server/src/server/io_stream.rs +28 -5
  19. data/ext/itsi_server/src/server/lifecycle_event.rs +1 -1
  20. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
  21. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
  22. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
  23. data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
  24. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -56
  25. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +5 -7
  26. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +5 -5
  27. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +7 -10
  28. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
  29. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
  30. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +4 -6
  31. data/ext/itsi_server/src/server/mod.rs +1 -0
  32. data/ext/itsi_server/src/server/process_worker.rs +3 -4
  33. data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +16 -12
  34. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +83 -31
  35. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +166 -142
  36. data/ext/itsi_server/src/server/signal.rs +37 -9
  37. data/ext/itsi_server/src/server/thread_worker.rs +84 -69
  38. data/ext/itsi_server/src/services/itsi_http_service.rs +43 -43
  39. data/ext/itsi_server/src/services/static_file_server.rs +28 -47
  40. data/lib/itsi/scheduler/version.rb +1 -1
  41. metadata +2 -1
@@ -1,4 +1,4 @@
1
- use super::file_watcher::{self};
1
+ use super::file_watcher::{self, WatcherCommand};
2
2
  use crate::{
3
3
  ruby_types::ITSI_SERVER_CONFIG,
4
4
  server::{
@@ -9,7 +9,7 @@ use crate::{
9
9
  use derive_more::Debug;
10
10
  use itsi_error::ItsiError;
11
11
  use itsi_rb_helpers::{call_with_gvl, print_rb_backtrace, HeapValue};
12
- use itsi_tracing::{set_format, set_level, set_target, set_target_filters};
12
+ use itsi_tracing::{error, set_format, set_level, set_target, set_target_filters};
13
13
  use magnus::{
14
14
  block::Proc,
15
15
  error::Result,
@@ -18,12 +18,12 @@ use magnus::{
18
18
  };
19
19
  use nix::{
20
20
  fcntl::{fcntl, FcntlArg, FdFlag},
21
- unistd::{close, dup},
21
+ unistd::dup,
22
22
  };
23
23
  use parking_lot::{Mutex, RwLock};
24
24
  use std::{
25
25
  collections::HashMap,
26
- os::fd::{AsRawFd, OwnedFd, RawFd},
26
+ os::fd::RawFd,
27
27
  path::PathBuf,
28
28
  str::FromStr,
29
29
  sync::{
@@ -32,7 +32,7 @@ use std::{
32
32
  },
33
33
  time::Duration,
34
34
  };
35
- use tracing::{debug, error};
35
+ use tracing::debug;
36
36
  static DEFAULT_BIND: &str = "http://localhost:3000";
37
37
  static ID_BUILD_CONFIG: LazyId = LazyId::new("build_config");
38
38
  static ID_RELOAD_EXEC: LazyId = LazyId::new("reload_exec");
@@ -44,7 +44,7 @@ pub struct ItsiServerConfig {
44
44
  pub itsi_config_proc: Arc<Option<HeapValue<Proc>>>,
45
45
  #[debug(skip)]
46
46
  pub server_params: Arc<RwLock<Arc<ServerParams>>>,
47
- pub watcher_fd: Arc<Option<OwnedFd>>,
47
+ pub watcher_fd: Arc<Option<file_watcher::WatcherPipes>>,
48
48
  }
49
49
 
50
50
  #[derive(Debug)]
@@ -72,13 +72,19 @@ 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>>,
78
84
  listener_info: Mutex<HashMap<String, i32>>,
79
85
  pub itsi_server_token_preference: ItsiServerTokenPreference,
80
86
  pub preloaded: AtomicBool,
81
- socket_opts: SocketOpts,
87
+ pub socket_opts: SocketOpts,
82
88
  preexisting_listeners: Option<String>,
83
89
  }
84
90
 
@@ -178,19 +184,19 @@ impl ServerParams {
178
184
  }
179
185
 
180
186
  fn from_rb_hash(rb_param_hash: RHash) -> Result<ServerParams> {
187
+ let num_cpus = num_cpus::get_physical() as u8;
181
188
  let workers = rb_param_hash
182
189
  .fetch::<_, Option<u8>>("workers")?
183
- .unwrap_or(num_cpus::get() as u8);
190
+ .unwrap_or(num_cpus);
184
191
  let worker_memory_limit: Option<u64> = rb_param_hash.fetch("worker_memory_limit")?;
185
192
  let silence: bool = rb_param_hash.fetch("silence")?;
186
193
  let multithreaded_reactor: bool = rb_param_hash
187
194
  .fetch::<_, Option<bool>>("multithreaded_reactor")?
188
- .unwrap_or(workers == 1);
195
+ .unwrap_or(workers <= (num_cpus / 3));
189
196
  let pin_worker_cores: bool = rb_param_hash
190
197
  .fetch::<_, Option<bool>>("pin_worker_cores")?
191
- .unwrap_or(true);
198
+ .unwrap_or(false);
192
199
  let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?;
193
-
194
200
  let hooks: Option<RHash> = rb_param_hash.fetch("hooks")?;
195
201
  let hooks = hooks
196
202
  .map(|rhash| -> Result<HashMap<String, HeapValue<Proc>>> {
@@ -281,6 +287,14 @@ impl ServerParams {
281
287
  set_target_filters(target_filters);
282
288
  }
283
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
+
284
298
  let binds: Option<Vec<String>> = rb_param_hash.fetch("binds")?;
285
299
  let binds = binds
286
300
  .unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
@@ -322,6 +336,12 @@ impl ServerParams {
322
336
  scheduler_class,
323
337
  ruby_thread_request_backlog_size,
324
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,
325
345
  binds,
326
346
  itsi_server_token_preference,
327
347
  socket_opts,
@@ -422,6 +442,10 @@ impl ItsiServerConfig {
422
442
  }
423
443
  }
424
444
 
445
+ pub fn use_reuse_port_load_balancing(&self) -> bool {
446
+ cfg!(target_os = "linux") && self.server_params.read().socket_opts.reuse_port
447
+ }
448
+
425
449
  /// Reload
426
450
  pub fn reload(self: Arc<Self>, cluster_worker: bool) -> Result<bool> {
427
451
  let server_params = call_with_gvl(|ruby| {
@@ -437,7 +461,8 @@ impl ItsiServerConfig {
437
461
  let requires_exec = if !is_single_mode && !server_params.preload {
438
462
  // In cluster mode children are cycled during a reload
439
463
  // and if preload is disabled, will get a clean memory slate,
440
- // so we don't need to exec.
464
+ // so we don't need to exec. We do need to rebind our listeners here.
465
+ server_params.setup_listeners()?;
441
466
  false
442
467
  } else {
443
468
  // In non-cluster mode, or when preloading is enabled, we shouldn't try to
@@ -532,6 +557,9 @@ impl ItsiServerConfig {
532
557
  }
533
558
 
534
559
  pub fn dup_fds(self: &Arc<Self>) -> Result<()> {
560
+ // Ensure the watcher is already stopped before duplicating file descriptors
561
+ // to prevent race conditions between closing the watcher FD and duplicating socket FDs
562
+
535
563
  let binding = self.server_params.read();
536
564
  let mut listener_info_guard = binding.listener_info.lock();
537
565
  let dupped_fd_map = listener_info_guard
@@ -557,8 +585,10 @@ impl ItsiServerConfig {
557
585
  }
558
586
 
559
587
  pub fn stop_watcher(self: &Arc<Self>) -> Result<()> {
560
- if let Some(r_fd) = self.watcher_fd.as_ref() {
561
- close(r_fd.as_raw_fd()).ok();
588
+ if let Some(pipes) = self.watcher_fd.as_ref() {
589
+ // Send explicit stop command to the watcher process
590
+ file_watcher::send_watcher_command(&pipes.write_fd, WatcherCommand::Stop)?;
591
+ // We don't close the pipes here - they'll be closed when the WatcherPipes is dropped
562
592
  }
563
593
  Ok(())
564
594
  }
@@ -573,8 +603,24 @@ impl ItsiServerConfig {
573
603
  pub async fn check_config(&self) -> bool {
574
604
  if let Some(errors) = self.get_config_errors().await {
575
605
  Self::print_config_errors(errors);
606
+ // Notify watcher that config check failed
607
+ if let Some(pipes) = self.watcher_fd.as_ref() {
608
+ if let Err(e) =
609
+ file_watcher::send_watcher_command(&pipes.write_fd, WatcherCommand::ConfigError)
610
+ {
611
+ error!("Failed to notify watcher of config error: {}", e);
612
+ }
613
+ }
576
614
  return false;
577
615
  }
616
+ // If we reach here, the config is valid
617
+ if let Some(pipes) = self.watcher_fd.as_ref() {
618
+ if let Err(e) =
619
+ file_watcher::send_watcher_command(&pipes.write_fd, WatcherCommand::Continue)
620
+ {
621
+ error!("Failed to notify watcher to continue: {}", e);
622
+ }
623
+ }
578
624
  true
579
625
  }
580
626
 
@@ -588,7 +634,8 @@ impl ItsiServerConfig {
588
634
  )
589
635
  })?;
590
636
 
591
- self.stop_watcher()?;
637
+ // Make sure we're not calling stop_watcher here to avoid double-stopping
638
+ // The watcher should be stopped earlier in the restart sequence
592
639
  call_with_gvl(|ruby| -> Result<()> {
593
640
  ruby.get_inner_ref(&ITSI_SERVER_CONFIG)
594
641
  .funcall::<_, _, Value>(*ID_RELOAD_EXEC, (listener_json,))?;
@@ -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
 
@@ -9,7 +9,7 @@ use super::bind_protocol::BindProtocol;
9
9
  use super::tls::ItsiTlsAcceptor;
10
10
  use itsi_error::{ItsiError, Result};
11
11
  use itsi_tracing::info;
12
- use socket2::{Domain, Protocol, Socket, Type};
12
+ use socket2::{Domain, Protocol, SockRef, Socket, Type};
13
13
  use std::fmt::Display;
14
14
  use std::net::{IpAddr, SocketAddr, TcpListener};
15
15
  use std::os::fd::{AsRawFd, FromRawFd, RawFd};
@@ -274,15 +274,53 @@ impl Display for Listener {
274
274
  }
275
275
 
276
276
  impl Listener {
277
- pub fn into_tokio_listener(self) -> TokioListener {
277
+ pub fn rebind_listener(listener: TcpListener) -> TcpListener {
278
+ let sock = SockRef::from(&listener);
279
+ let (reuse_address, reuse_port) = (
280
+ sock.reuse_address().unwrap_or(true),
281
+ sock.reuse_port().unwrap_or(true),
282
+ );
283
+
284
+ if !reuse_address || !reuse_port {
285
+ return listener;
286
+ }
287
+
288
+ let (ip, port) = sock
289
+ .local_addr()
290
+ .unwrap()
291
+ .as_socket()
292
+ .map(|addr| (addr.ip(), addr.port()))
293
+ .unwrap();
294
+
295
+ let socket_opts = SocketOpts {
296
+ reuse_address: sock.reuse_address().unwrap_or(true), // default: true
297
+ reuse_port: sock.reuse_port().unwrap_or(false), // default: false
298
+ nodelay: sock.nodelay().unwrap_or(false), // default: false
299
+ recv_buffer_size: sock.recv_buffer_size().unwrap_or(0),
300
+ send_buffer_size: sock.send_buffer_size().unwrap_or(0),
301
+ listen_backlog: 1024, // cannot query – pick sane default
302
+ };
303
+
304
+ connect_tcp_socket(ip, port, &socket_opts).unwrap()
305
+ }
306
+
307
+ pub fn into_tokio_listener(self, should_rebind: bool) -> TokioListener {
278
308
  match self {
279
- Listener::Tcp(listener) => {
309
+ Listener::Tcp(mut listener) => {
310
+ if should_rebind {
311
+ listener = Listener::rebind_listener(listener);
312
+ }
280
313
  TokioListener::Tcp(TokioTcpListener::from_std(listener).unwrap())
281
314
  }
282
- Listener::TcpTls((listener, acceptor)) => TokioListener::TcpTls(
283
- TokioTcpListener::from_std(listener).unwrap(),
284
- acceptor.clone(),
285
- ),
315
+ Listener::TcpTls((mut listener, acceptor)) => {
316
+ if should_rebind {
317
+ listener = Listener::rebind_listener(listener);
318
+ }
319
+ TokioListener::TcpTls(
320
+ TokioTcpListener::from_std(listener).unwrap(),
321
+ acceptor.clone(),
322
+ )
323
+ }
286
324
  Listener::Unix(listener) => {
287
325
  TokioListener::Unix(TokioUnixListener::from_std(listener).unwrap())
288
326
  }
@@ -0,0 +1,142 @@
1
+ use bytes::{Bytes, BytesMut};
2
+ use futures::Stream;
3
+ use std::convert::Infallible;
4
+ use std::future::Future;
5
+ use std::pin::Pin;
6
+ use std::task::{Context, Poll};
7
+ use std::time::Duration;
8
+ use tokio::sync::mpsc::Receiver;
9
+ use tokio::sync::watch;
10
+ use tokio::time::{sleep, Sleep};
11
+
12
+ use super::serve_strategy::single_mode::RunningPhase;
13
+
14
+ #[derive(Debug)]
15
+ pub struct FrameStream {
16
+ receiver: Receiver<Bytes>,
17
+ shutdown_rx: watch::Receiver<RunningPhase>,
18
+ drained: bool,
19
+ }
20
+
21
+ impl FrameStream {
22
+ pub fn new(receiver: Receiver<Bytes>, shutdown_rx: watch::Receiver<RunningPhase>) -> Self {
23
+ Self {
24
+ receiver,
25
+ shutdown_rx,
26
+ drained: false,
27
+ }
28
+ }
29
+ }
30
+
31
+ impl Stream for FrameStream {
32
+ type Item = Result<Bytes, Infallible>;
33
+
34
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
35
+ let this = self.get_mut();
36
+
37
+ if this.drained {
38
+ return Poll::Ready(None);
39
+ }
40
+
41
+ match Pin::new(&mut this.receiver).poll_recv(cx) {
42
+ Poll::Ready(Some(bytes)) => Poll::Ready(Some(Ok(bytes))),
43
+ Poll::Ready(None) => {
44
+ this.drained = true;
45
+ Poll::Ready(None)
46
+ }
47
+ Poll::Pending => {
48
+ if this.shutdown_rx.has_changed().unwrap_or(false)
49
+ && *this.shutdown_rx.borrow() == RunningPhase::ShutdownPending
50
+ {
51
+ while let Ok(bytes) = this.receiver.try_recv() {
52
+ return Poll::Ready(Some(Ok(bytes)));
53
+ }
54
+ this.drained = true;
55
+ return Poll::Ready(None);
56
+ }
57
+
58
+ Poll::Pending
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ /// BufferedStream wraps a stream of Bytes and coalesces chunks into a larger buffer,
65
+ /// flushing either after `max_flush_bytes` is reached or `max_flush_interval` elapses.
66
+ pub struct BufferedStream<S> {
67
+ inner: S,
68
+ buffer: BytesMut,
69
+ max_flush_bytes: usize,
70
+ max_flush_interval: Duration,
71
+ flush_deadline: Option<Pin<Box<Sleep>>>,
72
+ }
73
+
74
+ impl<S> BufferedStream<S> {
75
+ pub fn new(inner: S, max_flush_bytes: usize, max_flush_interval: Duration) -> Self {
76
+ Self {
77
+ inner,
78
+ buffer: BytesMut::with_capacity(max_flush_bytes),
79
+ max_flush_bytes,
80
+ max_flush_interval,
81
+ flush_deadline: None,
82
+ }
83
+ }
84
+ }
85
+
86
+ impl<S> Stream for BufferedStream<S>
87
+ where
88
+ S: Stream<Item = Result<Bytes, Infallible>> + Unpin,
89
+ {
90
+ type Item = Result<Bytes, Infallible>;
91
+
92
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
93
+ let this = self.get_mut();
94
+
95
+ loop {
96
+ // Flush on timer if needed
97
+ if let Some(deadline) = &mut this.flush_deadline {
98
+ if Pin::new(deadline).poll(cx).is_ready() && !this.buffer.is_empty() {
99
+ let flushed = this.buffer.split().freeze();
100
+ this.flush_deadline = None;
101
+ return Poll::Ready(Some(Ok(flushed)));
102
+ }
103
+ }
104
+
105
+ match Pin::new(&mut this.inner).poll_next(cx) {
106
+ Poll::Ready(Some(Ok(bytes))) => {
107
+ this.buffer.extend_from_slice(&bytes);
108
+
109
+ if bytes.is_empty() || this.buffer.len() >= this.max_flush_bytes {
110
+ let flushed = this.buffer.split().freeze();
111
+ this.flush_deadline = None;
112
+ return Poll::Ready(Some(Ok(flushed)));
113
+ }
114
+
115
+ if this.flush_deadline.is_none() {
116
+ this.flush_deadline = Some(Box::pin(sleep(this.max_flush_interval)));
117
+ }
118
+ }
119
+ Poll::Ready(None) => {
120
+ if this.buffer.is_empty() {
121
+ return Poll::Ready(None);
122
+ } else {
123
+ let flushed = this.buffer.split().freeze();
124
+ this.flush_deadline = None;
125
+ return Poll::Ready(Some(Ok(flushed)));
126
+ }
127
+ }
128
+ Poll::Pending => {
129
+ if let Some(deadline) = &mut this.flush_deadline {
130
+ let deadline = deadline.as_mut();
131
+ if deadline.poll(cx).is_ready() && !this.buffer.is_empty() {
132
+ let flushed = this.buffer.split().freeze();
133
+ this.flush_deadline = None;
134
+ return Poll::Ready(Some(Ok(flushed)));
135
+ }
136
+ }
137
+ return Poll::Pending;
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
@@ -1,13 +1,146 @@
1
- use std::convert::Infallible;
2
-
3
1
  use bytes::Bytes;
4
- use http::{Request, Response};
5
- use http_body_util::combinators::BoxBody;
6
- use hyper::body::Incoming;
2
+ use core::fmt;
3
+ use futures::Stream;
4
+ use futures_util::TryStreamExt;
5
+ use http::Request;
6
+ use http_body_util::{combinators::WithTrailers, BodyExt, Either, Empty, Full, StreamBody};
7
+ use hyper::body::{Body, Frame, Incoming, SizeHint};
8
+ use std::{
9
+ convert::Infallible,
10
+ pin::Pin,
11
+ task::{Context, Poll},
12
+ };
7
13
 
8
14
  use super::size_limited_incoming::SizeLimitedIncoming;
9
15
 
10
- pub type HttpResponse = Response<BoxBody<Bytes, Infallible>>;
16
+ type Inner = Either<Full<Bytes>, Empty<Bytes>>;
17
+
18
+ type BoxStream =
19
+ Pin<Box<dyn Stream<Item = Result<Frame<Bytes>, Infallible>> + Send + Sync + 'static>>;
20
+
21
+ pub struct PlainBody(Either<StreamBody<BoxStream>, Inner>);
22
+
23
+ impl fmt::Debug for PlainBody {
24
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25
+ match &self.0 {
26
+ Either::Left(_) => f.write_str("PlainBody::Stream(..)"),
27
+ Either::Right(inner) => match inner {
28
+ Either::Left(full) => f.debug_tuple("PlainBody::Full").field(full).finish(),
29
+ Either::Right(_) => f.write_str("PlainBody::Empty"),
30
+ },
31
+ }
32
+ }
33
+ }
34
+ type DynErr = Box<dyn std::error::Error + Send + Sync>;
35
+
36
+ impl Body for PlainBody {
37
+ type Data = Bytes;
38
+ type Error = DynErr;
39
+
40
+ fn poll_frame(
41
+ self: Pin<&mut Self>,
42
+ cx: &mut Context<'_>,
43
+ ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
44
+ unsafe { self.map_unchecked_mut(|s| &mut s.0) }.poll_frame(cx)
45
+ }
46
+
47
+ fn size_hint(&self) -> SizeHint {
48
+ self.0.size_hint()
49
+ }
50
+ }
51
+
52
+ impl PlainBody {
53
+ fn stream<S>(s: S) -> Self
54
+ where
55
+ S: Stream<Item = Result<Bytes, Infallible>> + Send + Sync + 'static,
56
+ {
57
+ let boxed: BoxStream = Box::pin(s.map_ok(Frame::data));
58
+ Self(Either::Left(StreamBody::new(boxed)))
59
+ }
60
+
61
+ fn full(bytes: Bytes) -> Self {
62
+ Self(Either::Right(Either::Left(Full::new(bytes))))
63
+ }
64
+
65
+ fn empty() -> Self {
66
+ Self(Either::Right(Either::Right(Empty::new())))
67
+ }
68
+ }
69
+
70
+ type BoxTrailers = Pin<
71
+ Box<dyn std::future::Future<Output = Option<Result<http::HeaderMap, DynErr>>> + Send + Sync>,
72
+ >;
73
+
74
+ pub enum HttpBody {
75
+ Plain(PlainBody),
76
+ WithT(WithTrailers<PlainBody, BoxTrailers>),
77
+ }
78
+
79
+ impl fmt::Debug for HttpBody {
80
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81
+ match self {
82
+ HttpBody::Plain(b) => f.debug_tuple("HttpBody::Plain").field(b).finish(),
83
+ HttpBody::WithT(_) => f.write_str("HttpBody::WithT(..)"),
84
+ }
85
+ }
86
+ }
87
+
88
+ impl Body for HttpBody {
89
+ type Data = Bytes;
90
+ type Error = DynErr;
91
+
92
+ fn poll_frame(
93
+ self: Pin<&mut Self>,
94
+ cx: &mut Context<'_>,
95
+ ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
96
+ unsafe {
97
+ match self.get_unchecked_mut() {
98
+ HttpBody::Plain(b) => Pin::new_unchecked(b).poll_frame(cx),
99
+ HttpBody::WithT(b) => Pin::new_unchecked(b).poll_frame(cx),
100
+ }
101
+ }
102
+ }
103
+
104
+ fn size_hint(&self) -> SizeHint {
105
+ match self {
106
+ HttpBody::Plain(b) => b.size_hint(),
107
+ HttpBody::WithT(b) => b.size_hint(),
108
+ }
109
+ }
110
+ }
111
+
112
+ impl HttpBody {
113
+ pub fn stream<S>(s: S) -> Self
114
+ where
115
+ S: Stream<Item = Result<Bytes, Infallible>> + Send + Sync + 'static,
116
+ {
117
+ HttpBody::Plain(PlainBody::stream(s))
118
+ }
119
+
120
+ pub fn full(bytes: Bytes) -> Self {
121
+ HttpBody::Plain(PlainBody::full(bytes))
122
+ }
123
+
124
+ pub fn empty() -> Self {
125
+ HttpBody::Plain(PlainBody::empty())
126
+ }
127
+
128
+ pub fn with_trailers<Fut>(self, fut: Fut) -> Self
129
+ where
130
+ Fut: std::future::Future<Output = Option<Result<http::HeaderMap, DynErr>>>
131
+ + Send
132
+ + Sync
133
+ + 'static,
134
+ {
135
+ let boxed: BoxTrailers = Box::pin(fut);
136
+ match self {
137
+ HttpBody::Plain(p) => HttpBody::WithT(p.with_trailers(boxed)),
138
+ already @ HttpBody::WithT(_) => already,
139
+ }
140
+ }
141
+ }
142
+
143
+ pub type HttpResponse = http::Response<HttpBody>;
11
144
  pub type HttpRequest = Request<SizeLimitedIncoming<Incoming>>;
12
145
 
13
146
  pub trait ConversionExt {
@@ -64,7 +197,7 @@ impl PathExt for str {
64
197
  if self == "/" {
65
198
  self
66
199
  } else {
67
- self.trim_end_matches("/")
200
+ self.trim_end_matches('/')
68
201
  }
69
202
  }
70
203
  }
@@ -91,7 +224,7 @@ impl RequestExt for HttpRequest {
91
224
  fn query_param(&self, query_name: &str) -> Option<&str> {
92
225
  self.uri()
93
226
  .query()
94
- .and_then(|query| query.split('&').find(|param| param.starts_with(query_name)))
95
- .map(|param| param.split('=').nth(1).unwrap_or(""))
227
+ .and_then(|q| q.split('&').find(|p| p.starts_with(query_name)))
228
+ .map(|p| p.split('=').nth(1).unwrap_or(""))
96
229
  }
97
230
  }
@@ -2,6 +2,7 @@ use pin_project::pin_project;
2
2
  use tokio::net::{TcpStream, UnixStream};
3
3
  use tokio_rustls::server::TlsStream;
4
4
 
5
+ use std::io::{self, IoSlice};
5
6
  use std::os::unix::io::{AsRawFd, RawFd};
6
7
  use std::pin::Pin;
7
8
  use std::task::{Context, Poll};
@@ -34,12 +35,12 @@ pub enum IoStream {
34
35
  }
35
36
 
36
37
  impl IoStream {
37
- pub fn addr(&self) -> SockAddr {
38
+ pub fn addr(&self) -> String {
38
39
  match self {
39
- IoStream::Tcp { addr, .. } => addr.clone(),
40
- IoStream::TcpTls { addr, .. } => addr.clone(),
41
- IoStream::Unix { addr, .. } => addr.clone(),
42
- IoStream::UnixTls { addr, .. } => addr.clone(),
40
+ IoStream::Tcp { addr, .. } => addr.to_string(),
41
+ IoStream::TcpTls { addr, .. } => addr.to_string(),
42
+ IoStream::Unix { addr, .. } => addr.to_string(),
43
+ IoStream::UnixTls { addr, .. } => addr.to_string(),
43
44
  }
44
45
  }
45
46
  }
@@ -90,6 +91,28 @@ impl AsyncWrite for IoStream {
90
91
  IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_shutdown(cx),
91
92
  }
92
93
  }
94
+
95
+ fn poll_write_vectored(
96
+ self: Pin<&mut Self>,
97
+ cx: &mut Context<'_>,
98
+ bufs: &[IoSlice<'_>],
99
+ ) -> Poll<io::Result<usize>> {
100
+ match self.project() {
101
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_write_vectored(cx, bufs),
102
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_write_vectored(cx, bufs),
103
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_write_vectored(cx, bufs),
104
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_write_vectored(cx, bufs),
105
+ }
106
+ }
107
+
108
+ fn is_write_vectored(&self) -> bool {
109
+ match self {
110
+ IoStream::Tcp { stream, .. } => stream.is_write_vectored(),
111
+ IoStream::TcpTls { stream, .. } => stream.is_write_vectored(),
112
+ IoStream::Unix { stream, .. } => stream.is_write_vectored(),
113
+ IoStream::UnixTls { stream, .. } => stream.is_write_vectored(),
114
+ }
115
+ }
93
116
  }
94
117
 
95
118
  impl AsRawFd for IoStream {
@@ -1,4 +1,4 @@
1
- #[derive(Debug, Clone)]
1
+ #[derive(Debug, Clone, PartialEq)]
2
2
  pub enum LifecycleEvent {
3
3
  Start,
4
4
  Shutdown,
@@ -3,7 +3,6 @@ use base64::{engine::general_purpose, Engine};
3
3
  use bytes::Bytes;
4
4
  use either::Either;
5
5
  use http::{Response, StatusCode};
6
- use http_body_util::{combinators::BoxBody, Full};
7
6
  use magnus::error::Result;
8
7
  use serde::{Deserialize, Serialize};
9
8
  use std::collections::HashMap;
@@ -11,7 +10,7 @@ use std::str;
11
10
  use tracing::debug;
12
11
 
13
12
  use crate::{
14
- server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
13
+ server::http_message_types::{HttpBody, HttpRequest, HttpResponse, RequestExt},
15
14
  services::{itsi_http_service::HttpRequestContext, password_hasher::verify_password_hash},
16
15
  };
17
16
 
@@ -34,7 +33,7 @@ impl AuthBasic {
34
33
  "WWW-Authenticate",
35
34
  format!("Basic realm=\"{}\"", self.realm),
36
35
  )
37
- .body(BoxBody::new(Full::new(Bytes::from("Unauthorized"))))
36
+ .body(HttpBody::full(Bytes::from("Unauthorized")))
38
37
  .unwrap()
39
38
  }
40
39
  }