itsi-server 0.2.16 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +3 -1
- data/exe/itsi +6 -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 +6 -15
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +26 -5
- 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 +87 -31
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +158 -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/http_request.rb +31 -39
- data/lib/itsi/http_response.rb +5 -0
- data/lib/itsi/rack_env_pool.rb +59 -0
- data/lib/itsi/server/config/dsl.rb +5 -4
- data/lib/itsi/server/config/middleware/proxy.rb +1 -1
- data/lib/itsi/server/config/middleware/rackup_file.rb +2 -2
- data/lib/itsi/server/config/options/auto_reload_config.rb +6 -2
- data/lib/itsi/server/config/options/include.rb +5 -2
- data/lib/itsi/server/config/options/pipeline_flush.md +16 -0
- data/lib/itsi/server/config/options/pipeline_flush.rb +19 -0
- data/lib/itsi/server/config/options/writev.md +25 -0
- data/lib/itsi/server/config/options/writev.rb +19 -0
- data/lib/itsi/server/config.rb +21 -8
- data/lib/itsi/server/default_config/Itsi.rb +1 -4
- data/lib/itsi/server/grpc/grpc_call.rb +2 -0
- data/lib/itsi/server/grpc/grpc_interface.rb +2 -2
- data/lib/itsi/server/rack/handler/itsi.rb +3 -1
- data/lib/itsi/server/rack_interface.rb +17 -12
- data/lib/itsi/server/scheduler_interface.rb +2 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +1 -0
- data/lib/ruby_lsp/itsi/addon.rb +12 -13
- metadata +7 -1
@@ -1,4 +1,9 @@
|
|
1
|
-
use
|
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::
|
10
|
-
use hyper::
|
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
|
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
|
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
|
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
|
58
|
-
|
59
|
-
|
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
|
-
) ->
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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.
|
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.
|
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(
|
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
|
-
|
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(
|
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
|
-
|
245
|
-
|
|
246
|
-
|
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.
|
263
|
-
if let Some(
|
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
|
269
|
-
self.
|
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
|
273
|
-
|
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
|
277
|
-
|
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
|
281
|
-
self.
|
290
|
+
pub fn close_write(&self) -> MagnusResult<bool> {
|
291
|
+
self.frame_writer.write().take();
|
292
|
+
Ok(true)
|
282
293
|
}
|
283
294
|
|
284
|
-
pub fn
|
285
|
-
|
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
|
291
|
-
|
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.
|
304
|
+
self.hijacked_socket.read().is_some()
|
305
305
|
}
|
306
306
|
|
307
|
-
pub fn
|
308
|
-
self.
|
309
|
-
|
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.
|
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
|
334
|
-
|
335
|
-
|
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.
|
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.
|
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.
|
432
|
-
if let Some(
|
433
|
-
|
434
|
-
.
|
435
|
-
.
|
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
|
-
|
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
|
-
|
36
|
-
|
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(
|
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>>,
|
@@ -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,
|
@@ -437,7 +457,8 @@ impl ItsiServerConfig {
|
|
437
457
|
let requires_exec = if !is_single_mode && !server_params.preload {
|
438
458
|
// In cluster mode children are cycled during a reload
|
439
459
|
// and if preload is disabled, will get a clean memory slate,
|
440
|
-
// 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()?;
|
441
462
|
false
|
442
463
|
} else {
|
443
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
|
|
@@ -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, no_rebind: bool) -> TokioListener {
|
278
308
|
match self {
|
279
|
-
Listener::Tcp(listener) => {
|
309
|
+
Listener::Tcp(mut listener) => {
|
310
|
+
if cfg!(target_os = "linux") && !no_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 cfg!(target_os = "linux") && !no_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
|
}
|