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,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
  }