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.
- checksums.yaml +4 -4
- data/Cargo.lock +1 -1
- data/ext/itsi_acme/Cargo.toml +1 -1
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_server/Cargo.toml +3 -1
- data/ext/itsi_server/src/lib.rs +6 -1
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +4 -4
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +64 -33
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +422 -110
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +62 -15
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
- data/ext/itsi_server/src/server/binds/listener.rs +45 -7
- data/ext/itsi_server/src/server/frame_stream.rs +142 -0
- data/ext/itsi_server/src/server/http_message_types.rs +142 -9
- data/ext/itsi_server/src/server/io_stream.rs +28 -5
- data/ext/itsi_server/src/server/lifecycle_event.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -56
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +5 -7
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +5 -5
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +7 -10
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +4 -6
- data/ext/itsi_server/src/server/mod.rs +1 -0
- data/ext/itsi_server/src/server/process_worker.rs +3 -4
- data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +16 -12
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +83 -31
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +166 -142
- data/ext/itsi_server/src/server/signal.rs +37 -9
- data/ext/itsi_server/src/server/thread_worker.rs +84 -69
- data/ext/itsi_server/src/services/itsi_http_service.rs +43 -43
- data/ext/itsi_server/src/services/static_file_server.rs +28 -47
- data/lib/itsi/scheduler/version.rb +1 -1
- 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::
|
21
|
+
unistd::dup,
|
22
22
|
};
|
23
23
|
use parking_lot::{Mutex, RwLock};
|
24
24
|
use std::{
|
25
25
|
collections::HashMap,
|
26
|
-
os::fd::
|
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::
|
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<
|
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
|
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
|
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(
|
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(
|
561
|
-
|
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
|
-
|
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
|
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)) =>
|
283
|
-
|
284
|
-
|
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
|
5
|
-
use
|
6
|
-
use
|
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
|
-
|
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(|
|
95
|
-
.map(|
|
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) ->
|
38
|
+
pub fn addr(&self) -> String {
|
38
39
|
match self {
|
39
|
-
IoStream::Tcp { addr, .. } => addr.
|
40
|
-
IoStream::TcpTls { addr, .. } => addr.
|
41
|
-
IoStream::Unix { addr, .. } => addr.
|
42
|
-
IoStream::UnixTls { addr, .. } => addr.
|
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 {
|
@@ -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(
|
36
|
+
.body(HttpBody::full(Bytes::from("Unauthorized")))
|
38
37
|
.unwrap()
|
39
38
|
}
|
40
39
|
}
|