itsi-server 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +75 -73
- 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.lock +1 -1
- data/ext/itsi_server/Cargo.toml +3 -1
- data/ext/itsi_server/extconf.rb +3 -1
- data/ext/itsi_server/src/lib.rs +7 -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 +6 -6
- 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 +71 -42
- 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 +32 -6
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
- data/ext/itsi_server/src/server/binds/listener.rs +49 -8
- data/ext/itsi_server/src/server/frame_stream.rs +142 -0
- data/ext/itsi_server/src/server/http_message_types.rs +143 -10
- 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 -58
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +6 -9
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +27 -42
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +65 -14
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +8 -11
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +21 -8
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +1 -5
- 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 +13 -6
- data/ext/itsi_server/src/server/mod.rs +1 -0
- data/ext/itsi_server/src/server/process_worker.rs +5 -5
- data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +87 -31
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +1 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +166 -206
- data/ext/itsi_server/src/server/signal.rs +37 -9
- data/ext/itsi_server/src/server/thread_worker.rs +92 -70
- data/ext/itsi_server/src/services/itsi_http_service.rs +67 -62
- data/ext/itsi_server/src/services/mime_types.rs +185 -183
- data/ext/itsi_server/src/services/rate_limiter.rs +16 -34
- data/ext/itsi_server/src/services/static_file_server.rs +35 -60
- 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/config_helpers.rb +1 -2
- data/lib/itsi/server/config/dsl.rb +5 -4
- data/lib/itsi/server/config/middleware/etag.md +3 -7
- data/lib/itsi/server/config/middleware/etag.rb +2 -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/listen_backlog.rb +1 -1
- 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/send_buffer_size.md +15 -0
- data/lib/itsi/server/config/options/send_buffer_size.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 +43 -31
- 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/route_tester.rb +1 -1
- 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 +10 -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>>,
|
@@ -82,7 +88,7 @@ pub struct ServerParams {
|
|
82
88
|
preexisting_listeners: Option<String>,
|
83
89
|
}
|
84
90
|
|
85
|
-
#[derive(Debug, Clone)]
|
91
|
+
#[derive(Debug, Clone, Copy)]
|
86
92
|
pub enum ItsiServerTokenPreference {
|
87
93
|
Version,
|
88
94
|
Name,
|
@@ -109,6 +115,7 @@ pub struct SocketOpts {
|
|
109
115
|
pub listen_backlog: usize,
|
110
116
|
pub nodelay: bool,
|
111
117
|
pub recv_buffer_size: usize,
|
118
|
+
pub send_buffer_size: usize,
|
112
119
|
}
|
113
120
|
|
114
121
|
impl ServerParams {
|
@@ -177,19 +184,19 @@ impl ServerParams {
|
|
177
184
|
}
|
178
185
|
|
179
186
|
fn from_rb_hash(rb_param_hash: RHash) -> Result<ServerParams> {
|
187
|
+
let num_cpus = num_cpus::get_physical() as u8;
|
180
188
|
let workers = rb_param_hash
|
181
189
|
.fetch::<_, Option<u8>>("workers")?
|
182
|
-
.unwrap_or(num_cpus
|
190
|
+
.unwrap_or(num_cpus);
|
183
191
|
let worker_memory_limit: Option<u64> = rb_param_hash.fetch("worker_memory_limit")?;
|
184
192
|
let silence: bool = rb_param_hash.fetch("silence")?;
|
185
193
|
let multithreaded_reactor: bool = rb_param_hash
|
186
194
|
.fetch::<_, Option<bool>>("multithreaded_reactor")?
|
187
|
-
.unwrap_or(workers
|
195
|
+
.unwrap_or(workers <= (num_cpus / 3));
|
188
196
|
let pin_worker_cores: bool = rb_param_hash
|
189
197
|
.fetch::<_, Option<bool>>("pin_worker_cores")?
|
190
|
-
.unwrap_or(
|
198
|
+
.unwrap_or(false);
|
191
199
|
let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?;
|
192
|
-
|
193
200
|
let hooks: Option<RHash> = rb_param_hash.fetch("hooks")?;
|
194
201
|
let hooks = hooks
|
195
202
|
.map(|rhash| -> Result<HashMap<String, HeapValue<Proc>>> {
|
@@ -248,6 +255,9 @@ impl ServerParams {
|
|
248
255
|
let recv_buffer_size: usize = rb_param_hash
|
249
256
|
.fetch::<_, Option<usize>>("recv_buffer_size")?
|
250
257
|
.unwrap_or(262_144);
|
258
|
+
let send_buffer_size: usize = rb_param_hash
|
259
|
+
.fetch::<_, Option<usize>>("send_buffer_size")?
|
260
|
+
.unwrap_or(262_144);
|
251
261
|
|
252
262
|
if let Some(level) = log_level {
|
253
263
|
set_level(&level);
|
@@ -277,6 +287,14 @@ impl ServerParams {
|
|
277
287
|
set_target_filters(target_filters);
|
278
288
|
}
|
279
289
|
|
290
|
+
let pipeline_flush: bool = rb_param_hash.fetch("pipeline_flush")?;
|
291
|
+
let writev: Option<bool> = rb_param_hash.fetch("writev")?;
|
292
|
+
let max_concurrent_streams: Option<u32> = rb_param_hash.fetch("max_concurrent_streams")?;
|
293
|
+
let max_local_error_reset_streams: Option<usize> =
|
294
|
+
rb_param_hash.fetch("max_local_error_reset_streams")?;
|
295
|
+
let max_header_list_size: u32 = rb_param_hash.fetch("max_header_list_size")?;
|
296
|
+
let max_send_buf_size: usize = rb_param_hash.fetch("max_send_buf_size")?;
|
297
|
+
|
280
298
|
let binds: Option<Vec<String>> = rb_param_hash.fetch("binds")?;
|
281
299
|
let binds = binds
|
282
300
|
.unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
|
@@ -296,6 +314,7 @@ impl ServerParams {
|
|
296
314
|
listen_backlog,
|
297
315
|
nodelay,
|
298
316
|
recv_buffer_size,
|
317
|
+
send_buffer_size,
|
299
318
|
};
|
300
319
|
let preexisting_listeners = rb_param_hash.delete::<_, Option<String>>("listeners")?;
|
301
320
|
|
@@ -317,6 +336,12 @@ impl ServerParams {
|
|
317
336
|
scheduler_class,
|
318
337
|
ruby_thread_request_backlog_size,
|
319
338
|
oob_gc_responses_threshold,
|
339
|
+
pipeline_flush,
|
340
|
+
writev,
|
341
|
+
max_concurrent_streams,
|
342
|
+
max_local_error_reset_streams,
|
343
|
+
max_header_list_size,
|
344
|
+
max_send_buf_size,
|
320
345
|
binds,
|
321
346
|
itsi_server_token_preference,
|
322
347
|
socket_opts,
|
@@ -432,7 +457,8 @@ impl ItsiServerConfig {
|
|
432
457
|
let requires_exec = if !is_single_mode && !server_params.preload {
|
433
458
|
// In cluster mode children are cycled during a reload
|
434
459
|
// and if preload is disabled, will get a clean memory slate,
|
435
|
-
// so we don't need to exec.
|
460
|
+
// so we don't need to exec. We do need to rebind our listeners here.
|
461
|
+
server_params.setup_listeners()?;
|
436
462
|
false
|
437
463
|
} else {
|
438
464
|
// In non-cluster mode, or when preloading is enabled, we shouldn't try to
|
@@ -64,7 +64,7 @@ impl ItsiServer {
|
|
64
64
|
Ok(if server_config.server_params.read().workers > 1 {
|
65
65
|
ServeStrategy::Cluster(Arc::new(ClusterMode::new(server_config.clone())))
|
66
66
|
} else {
|
67
|
-
ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone())?))
|
67
|
+
ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone(), 0)?))
|
68
68
|
})
|
69
69
|
}
|
70
70
|
|