itsi 0.1.14 → 0.1.19
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 +126 -272
- data/Cargo.toml +6 -0
- data/crates/itsi_error/Cargo.toml +1 -0
- data/crates/itsi_error/src/lib.rs +100 -10
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
- data/crates/itsi_server/Cargo.toml +12 -11
- data/crates/itsi_server/src/default_responses/html/401.html +68 -0
- data/crates/itsi_server/src/default_responses/html/403.html +68 -0
- data/crates/itsi_server/src/default_responses/html/404.html +68 -0
- data/crates/itsi_server/src/default_responses/html/413.html +71 -0
- data/crates/itsi_server/src/default_responses/html/429.html +68 -0
- data/crates/itsi_server/src/default_responses/html/500.html +71 -0
- data/crates/itsi_server/src/default_responses/html/502.html +71 -0
- data/crates/itsi_server/src/default_responses/html/503.html +68 -0
- data/crates/itsi_server/src/default_responses/html/504.html +69 -0
- data/crates/itsi_server/src/default_responses/html/index.html +238 -0
- data/crates/itsi_server/src/default_responses/json/401.json +6 -0
- data/crates/itsi_server/src/default_responses/json/403.json +6 -0
- data/crates/itsi_server/src/default_responses/json/404.json +6 -0
- data/crates/itsi_server/src/default_responses/json/413.json +6 -0
- data/crates/itsi_server/src/default_responses/json/429.json +6 -0
- data/crates/itsi_server/src/default_responses/json/500.json +6 -0
- data/crates/itsi_server/src/default_responses/json/502.json +6 -0
- data/crates/itsi_server/src/default_responses/json/503.json +6 -0
- data/crates/itsi_server/src/default_responses/json/504.json +6 -0
- data/crates/itsi_server/src/default_responses/mod.rs +11 -0
- data/crates/itsi_server/src/lib.rs +58 -26
- data/crates/itsi_server/src/prelude.rs +2 -0
- data/crates/itsi_server/src/ruby_types/README.md +21 -0
- data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
- data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
- data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
- data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
- data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
- data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
- data/crates/itsi_server/src/server/binds/mod.rs +4 -0
- data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
- data/crates/itsi_server/src/server/http_message_types.rs +97 -0
- data/crates/itsi_server/src/server/io_stream.rs +2 -1
- data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
- data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +50 -29
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
- data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
- data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
- data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
- data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
- data/crates/itsi_server/src/server/mod.rs +3 -9
- data/crates/itsi_server/src/server/process_worker.rs +21 -3
- data/crates/itsi_server/src/server/request_job.rs +2 -2
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
- data/crates/itsi_server/src/server/signal.rs +24 -41
- data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/crates/itsi_server/src/server/thread_worker.rs +59 -28
- data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
- data/crates/itsi_server/src/services/mime_types.rs +1416 -0
- data/crates/itsi_server/src/services/mod.rs +6 -0
- data/crates/itsi_server/src/services/password_hasher.rs +83 -0
- data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
- data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
- data/crates/itsi_tracing/src/lib.rs +145 -55
- data/{Itsi.rb → foo/Itsi.rb} +6 -9
- data/gems/scheduler/Cargo.lock +7 -0
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/test/helpers/test_helper.rb +0 -1
- data/gems/scheduler/test/test_address_resolve.rb +0 -1
- data/gems/scheduler/test/test_network_io.rb +1 -1
- data/gems/scheduler/test/test_process_wait.rb +0 -1
- data/gems/server/Cargo.lock +126 -272
- data/gems/server/exe/itsi +65 -19
- data/gems/server/itsi-server.gemspec +4 -3
- data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
- data/gems/server/lib/itsi/http_request.rb +117 -17
- data/gems/server/lib/itsi/http_response.rb +2 -0
- data/gems/server/lib/itsi/passfile.rb +109 -0
- data/gems/server/lib/itsi/server/config/dsl.rb +171 -99
- data/gems/server/lib/itsi/server/config.rb +58 -23
- data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
- data/gems/server/lib/itsi/server/default_app/index.html +113 -89
- data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
- data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
- data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
- data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
- data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
- data/gems/server/lib/itsi/server/route_tester.rb +107 -0
- data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
- data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
- data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +82 -12
- data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
- data/gems/server/lib/shell_completions/completions.rb +26 -0
- data/gems/server/test/helpers/test_helper.rb +2 -1
- data/lib/itsi/version.rb +1 -1
- data/sandbox/README.md +5 -0
- data/sandbox/itsi_file/Gemfile +4 -2
- data/sandbox/itsi_file/Gemfile.lock +48 -6
- data/sandbox/itsi_file/Itsi.rb +327 -129
- data/sandbox/itsi_file/call.json +1 -0
- data/sandbox/itsi_file/echo_client/Gemfile +10 -0
- data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
- data/sandbox/itsi_file/echo_client/README.md +95 -0
- data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
- data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
- data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
- data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
- data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
- data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
- data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
- data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
- data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
- data/sandbox/itsi_sandbox_async/config.ru +0 -1
- data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
- data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
- data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
- data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
- data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
- data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
- data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
- data/sandbox/itsi_sinatra/app.rb +0 -1
- data/sandbox/static_files/.env +1 -0
- data/sandbox/static_files/404.html +25 -0
- data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
- data/sandbox/static_files/about.html +68 -0
- data/sandbox/static_files/tiny.html +1 -0
- data/sandbox/static_files/writebook.zip +0 -0
- data/tasks.txt +28 -33
- metadata +87 -26
- data/crates/itsi_error/src/from.rs +0 -68
- data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
- data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
- data/crates/itsi_server/src/server/itsi_service.rs +0 -172
- data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
- data/crates/itsi_server/src/server/types.rs +0 -43
- data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
- data/sandbox/itsi_file/public/assets/index.html +0 -1
- /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
- /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
- /data/crates/itsi_server/src/{server → services}/cache_store.rs +0 -0
@@ -1,14 +1,14 @@
|
|
1
1
|
use derive_more::Debug;
|
2
2
|
use futures::StreamExt;
|
3
|
-
use http::{request::Parts, Response, StatusCode, Version};
|
3
|
+
use http::{header::CONTENT_LENGTH, request::Parts, Response, StatusCode, Version};
|
4
4
|
use http_body_util::{combinators::BoxBody, BodyExt, Empty};
|
5
|
-
use itsi_error::
|
5
|
+
use itsi_error::CLIENT_CONNECTION_CLOSED;
|
6
6
|
use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
|
7
7
|
use itsi_tracing::{debug, error};
|
8
8
|
use magnus::{
|
9
9
|
block::Proc,
|
10
10
|
error::{ErrorType, Result as MagnusResult},
|
11
|
-
Error,
|
11
|
+
Error, RHash, Symbol,
|
12
12
|
};
|
13
13
|
use magnus::{
|
14
14
|
value::{LazyId, ReprValue},
|
@@ -21,11 +21,14 @@ use super::{
|
|
21
21
|
itsi_body_proxy::{big_bytes::BigBytes, ItsiBody, ItsiBodyProxy},
|
22
22
|
itsi_http_response::ItsiHttpResponse,
|
23
23
|
};
|
24
|
-
use crate::
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
use crate::{
|
25
|
+
server::{
|
26
|
+
byte_frame::ByteFrame,
|
27
|
+
http_message_types::{HttpRequest, HttpResponse},
|
28
|
+
request_job::RequestJob,
|
29
|
+
size_limited_incoming::MaxBodySizeReached,
|
30
|
+
},
|
31
|
+
services::itsi_http_service::HttpRequestContext,
|
29
32
|
};
|
30
33
|
|
31
34
|
static ID_MESSAGE: LazyId = LazyId::new("message");
|
@@ -40,7 +43,8 @@ pub struct ItsiHttpRequest {
|
|
40
43
|
pub response: ItsiHttpResponse,
|
41
44
|
pub start: Instant,
|
42
45
|
#[debug(skip)]
|
43
|
-
pub context:
|
46
|
+
pub context: HttpRequestContext,
|
47
|
+
pub script_name: String,
|
44
48
|
}
|
45
49
|
|
46
50
|
impl fmt::Display for ItsiHttpRequest {
|
@@ -73,7 +77,7 @@ impl ItsiHttpRequest {
|
|
73
77
|
}
|
74
78
|
}
|
75
79
|
}
|
76
|
-
fn content_type_str(&self) -> &str {
|
80
|
+
pub fn content_type_str(&self) -> &str {
|
77
81
|
self.parts
|
78
82
|
.headers
|
79
83
|
.get("Content-Type")
|
@@ -85,10 +89,49 @@ impl ItsiHttpRequest {
|
|
85
89
|
self.content_type_str() == "application/json"
|
86
90
|
}
|
87
91
|
|
92
|
+
pub fn url_params(&self) -> magnus::error::Result<RHash> {
|
93
|
+
let captures = self
|
94
|
+
.context
|
95
|
+
.matching_pattern
|
96
|
+
.as_ref()
|
97
|
+
.and_then(|re| re.captures(self.parts.uri.path()));
|
98
|
+
if let Some(caps) = &captures {
|
99
|
+
let re = self.context.matching_pattern.as_ref().unwrap();
|
100
|
+
let params = RHash::with_capacity(caps.len());
|
101
|
+
for (i, group_name) in re.capture_names().enumerate().skip(1) {
|
102
|
+
if let Some(name) = group_name {
|
103
|
+
if let Some(m) = caps.get(i) {
|
104
|
+
// Insert into the hash: key is the group name, value is the match.
|
105
|
+
params.aset(Symbol::new(name), m.as_str())?;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
Ok(params)
|
110
|
+
} else {
|
111
|
+
Ok(RHash::new())
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
88
115
|
pub fn is_html(&self) -> bool {
|
89
116
|
self.content_type_str() == "text/html"
|
90
117
|
}
|
91
118
|
|
119
|
+
pub fn is_url_encoded(&self) -> bool {
|
120
|
+
self.content_type_str() == "application/x-www-form-urlencoded"
|
121
|
+
}
|
122
|
+
|
123
|
+
pub fn is_multipart(&self) -> bool {
|
124
|
+
self.content_type_str().starts_with("multipart/form-data")
|
125
|
+
}
|
126
|
+
|
127
|
+
pub fn content_length(&self) -> Option<u64> {
|
128
|
+
self.parts
|
129
|
+
.headers
|
130
|
+
.get(CONTENT_LENGTH)
|
131
|
+
.and_then(|hv| hv.to_str().ok())
|
132
|
+
.and_then(|s| s.parse().ok())
|
133
|
+
}
|
134
|
+
|
92
135
|
pub fn process(self, ruby: &Ruby, app_proc: Arc<HeapValue<Proc>>) -> magnus::error::Result<()> {
|
93
136
|
let response = self.response.clone();
|
94
137
|
let result = app_proc.call::<_, Value>((self,));
|
@@ -117,37 +160,48 @@ impl ItsiHttpRequest {
|
|
117
160
|
pub(crate) async fn process_request(
|
118
161
|
app: Arc<HeapValue<Proc>>,
|
119
162
|
hyper_request: HttpRequest,
|
120
|
-
context: &
|
163
|
+
context: &HttpRequestContext,
|
164
|
+
script_name: String,
|
165
|
+
nonblocking: bool,
|
121
166
|
) -> itsi_error::Result<HttpResponse> {
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
167
|
+
match ItsiHttpRequest::new(hyper_request, context, script_name).await {
|
168
|
+
Ok((request, mut receiver)) => {
|
169
|
+
let shutdown_channel = context.service.shutdown_channel.clone();
|
170
|
+
let response = request.response.clone();
|
171
|
+
let sender = if nonblocking {
|
172
|
+
&context.nonblocking_sender
|
173
|
+
} else {
|
174
|
+
&context.sender
|
175
|
+
};
|
176
|
+
match sender
|
177
|
+
.send(RequestJob::ProcessHttpRequest(request, app))
|
178
|
+
.await
|
179
|
+
{
|
180
|
+
Err(err) => {
|
181
|
+
error!("Error occurred: {}", err);
|
182
|
+
let mut response = Response::new(BoxBody::new(Empty::new()));
|
183
|
+
*response.status_mut() = StatusCode::BAD_REQUEST;
|
184
|
+
Ok(response)
|
185
|
+
}
|
186
|
+
_ => match receiver.recv().await {
|
187
|
+
Some(first_frame) => Ok(response
|
188
|
+
.build(first_frame, receiver, shutdown_channel)
|
189
|
+
.await),
|
190
|
+
None => Ok(response
|
191
|
+
.build(ByteFrame::Empty, receiver, shutdown_channel)
|
192
|
+
.await),
|
193
|
+
},
|
194
|
+
}
|
135
195
|
}
|
136
|
-
|
137
|
-
Some(first_frame) => Ok(response
|
138
|
-
.build(first_frame, receiver, shutdown_channel)
|
139
|
-
.await),
|
140
|
-
None => Ok(response
|
141
|
-
.build(ByteFrame::Empty, receiver, shutdown_channel)
|
142
|
-
.await),
|
143
|
-
},
|
196
|
+
Err(err_resp) => Ok(err_resp),
|
144
197
|
}
|
145
198
|
}
|
146
199
|
|
147
200
|
pub(crate) async fn new(
|
148
201
|
request: HttpRequest,
|
149
|
-
context: &
|
150
|
-
|
202
|
+
context: &HttpRequestContext,
|
203
|
+
script_name: String,
|
204
|
+
) -> Result<(ItsiHttpRequest, mpsc::Receiver<ByteFrame>), HttpResponse> {
|
151
205
|
let (parts, body) = request.into_parts();
|
152
206
|
let body = if context.server_params.streamable_body {
|
153
207
|
ItsiBody::Stream(ItsiBodyProxy::new(body))
|
@@ -155,23 +209,32 @@ impl ItsiHttpRequest {
|
|
155
209
|
let mut body_bytes = BigBytes::new();
|
156
210
|
let mut stream = body.into_data_stream();
|
157
211
|
while let Some(chunk) = stream.next().await {
|
158
|
-
|
159
|
-
|
212
|
+
match chunk {
|
213
|
+
Ok(byte_array) => body_bytes.write_all(&byte_array).unwrap(),
|
214
|
+
Err(e) => {
|
215
|
+
let mut err_resp = Response::new(BoxBody::new(Empty::new()));
|
216
|
+
if e.downcast_ref::<MaxBodySizeReached>().is_some() {
|
217
|
+
*err_resp.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
|
218
|
+
}
|
219
|
+
return Err(err_resp);
|
220
|
+
}
|
221
|
+
}
|
160
222
|
}
|
161
223
|
ItsiBody::Buffered(body_bytes)
|
162
224
|
};
|
163
225
|
let response_channel = mpsc::channel::<ByteFrame>(100);
|
164
|
-
(
|
226
|
+
Ok((
|
165
227
|
Self {
|
166
228
|
context: context.clone(),
|
167
229
|
version: parts.version,
|
168
230
|
response: ItsiHttpResponse::new(parts.clone(), response_channel.0),
|
169
231
|
start: Instant::now(),
|
232
|
+
script_name,
|
170
233
|
body,
|
171
234
|
parts,
|
172
235
|
},
|
173
236
|
response_channel.1,
|
174
|
-
)
|
237
|
+
))
|
175
238
|
}
|
176
239
|
|
177
240
|
pub(crate) fn path(&self) -> MagnusResult<&str> {
|
@@ -179,12 +242,12 @@ impl ItsiHttpRequest {
|
|
179
242
|
.parts
|
180
243
|
.uri
|
181
244
|
.path()
|
182
|
-
.strip_prefix(&self.
|
245
|
+
.strip_prefix(&self.script_name)
|
183
246
|
.unwrap_or(self.parts.uri.path()))
|
184
247
|
}
|
185
248
|
|
186
249
|
pub(crate) fn script_name(&self) -> MagnusResult<&str> {
|
187
|
-
Ok(&self.
|
250
|
+
Ok(&self.script_name)
|
188
251
|
}
|
189
252
|
|
190
253
|
pub(crate) fn query_string(&self) -> MagnusResult<&str> {
|
@@ -2,8 +2,9 @@ use bytes::{Bytes, BytesMut};
|
|
2
2
|
use derive_more::Debug;
|
3
3
|
use futures::stream::{unfold, StreamExt};
|
4
4
|
use http::{
|
5
|
-
header::
|
6
|
-
|
5
|
+
header::{ACCEPT, TRANSFER_ENCODING},
|
6
|
+
request::Parts,
|
7
|
+
HeaderMap, HeaderName, HeaderValue, Request, Response, StatusCode,
|
7
8
|
};
|
8
9
|
use http_body_util::{combinators::BoxBody, Empty, Full, StreamBody};
|
9
10
|
use hyper::{body::Frame, upgrade::Upgraded};
|
@@ -32,7 +33,8 @@ use tokio_util::io::ReaderStream;
|
|
32
33
|
use tracing::warn;
|
33
34
|
|
34
35
|
use crate::server::{
|
35
|
-
byte_frame::ByteFrame,
|
36
|
+
byte_frame::ByteFrame, http_message_types::HttpResponse,
|
37
|
+
serve_strategy::single_mode::RunningPhase,
|
36
38
|
};
|
37
39
|
|
38
40
|
#[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)]
|
@@ -297,11 +299,12 @@ impl ItsiHttpResponse {
|
|
297
299
|
self.data.response_writer.write().take();
|
298
300
|
Ok(true)
|
299
301
|
}
|
300
|
-
|
302
|
+
|
303
|
+
pub fn accept_str(&self) -> &str {
|
301
304
|
self.data
|
302
305
|
.parts
|
303
306
|
.headers
|
304
|
-
.get(
|
307
|
+
.get(ACCEPT)
|
305
308
|
.and_then(|hv| hv.to_str().ok()) // handle invalid utf-8
|
306
309
|
.unwrap_or("application/x-www-form-urlencoded")
|
307
310
|
}
|
@@ -71,7 +71,7 @@ const DEBOUNCE_DURATION: Duration = Duration::from_millis(500);
|
|
71
71
|
pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<Option<OwnedFd>> {
|
72
72
|
let (r_fd, w_fd): (OwnedFd, OwnedFd) = pipe().map_err(|e| {
|
73
73
|
magnus::Error::new(
|
74
|
-
magnus::exception::
|
74
|
+
magnus::exception::standard_error(),
|
75
75
|
format!("Failed to create watcher pipe: {}", e),
|
76
76
|
)
|
77
77
|
})?;
|
@@ -79,7 +79,7 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
|
|
79
79
|
let fork_result = unsafe {
|
80
80
|
fork().map_err(|e| {
|
81
81
|
magnus::Error::new(
|
82
|
-
magnus::exception::
|
82
|
+
magnus::exception::standard_error(),
|
83
83
|
format!("Failed to fork file watcher: {}", e),
|
84
84
|
)
|
85
85
|
})
|
@@ -107,13 +107,13 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
|
|
107
107
|
let base_dir = extract_and_canonicalize_base_dir(&pattern);
|
108
108
|
let glob = Glob::new(&pattern).map_err(|e| {
|
109
109
|
magnus::Error::new(
|
110
|
-
magnus::exception::
|
110
|
+
magnus::exception::standard_error(),
|
111
111
|
format!("Failed to create watch glob: {}", e),
|
112
112
|
)
|
113
113
|
})?;
|
114
114
|
let glob_set = GlobSetBuilder::new().add(glob).build().map_err(|e| {
|
115
115
|
magnus::Error::new(
|
116
|
-
magnus::exception::
|
116
|
+
magnus::exception::standard_error(),
|
117
117
|
format!("Failed to create watch glob set: {}", e),
|
118
118
|
)
|
119
119
|
})?;
|
@@ -1,11 +1,14 @@
|
|
1
1
|
use super::file_watcher::{self};
|
2
2
|
use crate::{
|
3
3
|
ruby_types::ITSI_SERVER_CONFIG,
|
4
|
-
server::{
|
4
|
+
server::{
|
5
|
+
binds::{bind::Bind, listener::Listener},
|
6
|
+
middleware_stack::MiddlewareSet,
|
7
|
+
},
|
5
8
|
};
|
6
9
|
use derive_more::Debug;
|
7
|
-
use itsi_rb_helpers::{call_with_gvl, print_rb_backtrace,
|
8
|
-
use itsi_tracing::set_level;
|
10
|
+
use itsi_rb_helpers::{call_with_gvl, print_rb_backtrace, HeapValue};
|
11
|
+
use itsi_tracing::{set_format, set_level, set_target};
|
9
12
|
use magnus::{
|
10
13
|
block::Proc,
|
11
14
|
error::Result,
|
@@ -22,8 +25,8 @@ use std::{
|
|
22
25
|
os::fd::{AsRawFd, OwnedFd, RawFd},
|
23
26
|
path::PathBuf,
|
24
27
|
sync::{Arc, OnceLock},
|
28
|
+
time::Duration,
|
25
29
|
};
|
26
|
-
|
27
30
|
static DEFAULT_BIND: &str = "http://localhost:3000";
|
28
31
|
static ID_BUILD_CONFIG: LazyId = LazyId::new("build_config");
|
29
32
|
static ID_RELOAD_EXEC: LazyId = LazyId::new("reload_exec");
|
@@ -48,16 +51,17 @@ pub struct ServerParams {
|
|
48
51
|
pub hooks: HashMap<String, HeapValue<Proc>>,
|
49
52
|
pub preload: bool,
|
50
53
|
|
54
|
+
pub request_timeout: Option<Duration>,
|
51
55
|
pub notify_watchers: Option<Vec<(String, Vec<Vec<String>>)>>,
|
52
56
|
/// Worker params
|
53
57
|
pub threads: u8,
|
54
|
-
pub
|
58
|
+
pub scheduler_threads: Option<u8>,
|
55
59
|
pub streamable_body: bool,
|
56
60
|
pub multithreaded_reactor: bool,
|
61
|
+
pub pin_worker_cores: bool,
|
57
62
|
pub scheduler_class: Option<String>,
|
58
63
|
pub oob_gc_responses_threshold: Option<u64>,
|
59
64
|
pub middleware_loader: HeapValue<Proc>,
|
60
|
-
pub default_app_loader: HeapValue<Proc>,
|
61
65
|
pub middleware: OnceLock<MiddlewareSet>,
|
62
66
|
pub binds: Vec<Bind>,
|
63
67
|
#[debug(skip)]
|
@@ -75,7 +79,6 @@ impl ServerParams {
|
|
75
79
|
{
|
76
80
|
ruby.require("itsi/scheduler")?;
|
77
81
|
}
|
78
|
-
let default_app: HeapVal = self.default_app_loader.call::<_, Value>(())?.into();
|
79
82
|
let middleware = MiddlewareSet::new(
|
80
83
|
self.middleware_loader
|
81
84
|
.call::<_, Option<Value>>(())
|
@@ -85,7 +88,6 @@ impl ServerParams {
|
|
85
88
|
}
|
86
89
|
})?
|
87
90
|
.map(|mw| mw.into()),
|
88
|
-
default_app,
|
89
91
|
)?;
|
90
92
|
self.middleware.set(middleware).map_err(|_| {
|
91
93
|
magnus::Error::new(
|
@@ -104,7 +106,12 @@ impl ServerParams {
|
|
104
106
|
.unwrap_or(num_cpus::get() as u8);
|
105
107
|
let worker_memory_limit: Option<u64> = rb_param_hash.fetch("worker_memory_limit")?;
|
106
108
|
let silence: bool = rb_param_hash.fetch("silence")?;
|
107
|
-
let multithreaded_reactor: bool = rb_param_hash
|
109
|
+
let multithreaded_reactor: bool = rb_param_hash
|
110
|
+
.fetch::<_, Option<bool>>("multithreaded_reactor")?
|
111
|
+
.unwrap_or(workers == 1);
|
112
|
+
let pin_worker_cores: bool = rb_param_hash
|
113
|
+
.fetch::<_, Option<bool>>("pin_worker_cores")?
|
114
|
+
.unwrap_or(true);
|
108
115
|
let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?;
|
109
116
|
|
110
117
|
let hooks: Option<RHash> = rb_param_hash.fetch("hooks")?;
|
@@ -125,22 +132,34 @@ impl ServerParams {
|
|
125
132
|
.transpose()?
|
126
133
|
.unwrap_or_default();
|
127
134
|
let preload: bool = rb_param_hash.fetch("preload")?;
|
135
|
+
let request_timeout: Option<u64> = rb_param_hash.fetch("request_timeout")?;
|
136
|
+
let request_timeout = request_timeout.map(Duration::from_secs);
|
137
|
+
|
128
138
|
let notify_watchers: Option<Vec<(String, Vec<Vec<String>>)>> =
|
129
139
|
rb_param_hash.fetch("notify_watchers")?;
|
130
140
|
let threads: u8 = rb_param_hash.fetch("threads")?;
|
131
|
-
let
|
141
|
+
let scheduler_threads: Option<u8> = rb_param_hash.fetch("scheduler_threads")?;
|
132
142
|
let streamable_body: bool = rb_param_hash.fetch("streamable_body")?;
|
133
143
|
let scheduler_class: Option<String> = rb_param_hash.fetch("scheduler_class")?;
|
134
144
|
let oob_gc_responses_threshold: Option<u64> =
|
135
145
|
rb_param_hash.fetch("oob_gc_responses_threshold")?;
|
136
146
|
let middleware_loader: Proc = rb_param_hash.fetch("middleware_loader")?;
|
137
|
-
let default_app_loader: Proc = rb_param_hash.fetch("default_app_loader")?;
|
138
147
|
let log_level: Option<String> = rb_param_hash.fetch("log_level")?;
|
148
|
+
let log_target: Option<String> = rb_param_hash.fetch("log_target")?;
|
149
|
+
let log_format: Option<String> = rb_param_hash.fetch("log_format")?;
|
139
150
|
|
140
151
|
if let Some(level) = log_level {
|
141
152
|
set_level(&level);
|
142
153
|
}
|
143
154
|
|
155
|
+
if let Some(target) = log_target {
|
156
|
+
set_target(&target);
|
157
|
+
}
|
158
|
+
|
159
|
+
if let Some(format) = log_format {
|
160
|
+
set_format(&format);
|
161
|
+
}
|
162
|
+
|
144
163
|
let binds: Option<Vec<String>> = rb_param_hash.fetch("binds")?;
|
145
164
|
let binds = binds
|
146
165
|
.unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
|
@@ -154,7 +173,7 @@ impl ServerParams {
|
|
154
173
|
let bind_to_fd_map: HashMap<String, i32> = serde_json::from_str(&preexisting_listeners)
|
155
174
|
.map_err(|e| {
|
156
175
|
magnus::Error::new(
|
157
|
-
magnus::exception::
|
176
|
+
magnus::exception::standard_error(),
|
158
177
|
format!("Invalid listener info: {}", e),
|
159
178
|
)
|
160
179
|
})?;
|
@@ -196,12 +215,14 @@ impl ServerParams {
|
|
196
215
|
worker_memory_limit,
|
197
216
|
silence,
|
198
217
|
multithreaded_reactor,
|
218
|
+
pin_worker_cores,
|
199
219
|
shutdown_timeout,
|
200
220
|
hooks,
|
201
221
|
preload,
|
222
|
+
request_timeout,
|
202
223
|
notify_watchers,
|
203
224
|
threads,
|
204
|
-
|
225
|
+
scheduler_threads,
|
205
226
|
streamable_body,
|
206
227
|
scheduler_class,
|
207
228
|
oob_gc_responses_threshold,
|
@@ -209,7 +230,6 @@ impl ServerParams {
|
|
209
230
|
listener_info: Mutex::new(listener_info),
|
210
231
|
listeners: Mutex::new(listeners),
|
211
232
|
middleware_loader: middleware_loader.into(),
|
212
|
-
default_app_loader: default_app_loader.into(),
|
213
233
|
middleware: OnceLock::new(),
|
214
234
|
})
|
215
235
|
}
|
@@ -310,13 +330,13 @@ impl ItsiServerConfig {
|
|
310
330
|
.map(|(str, fd)| {
|
311
331
|
let dupped_fd = dup(*fd).map_err(|errno| {
|
312
332
|
magnus::Error::new(
|
313
|
-
magnus::exception::
|
333
|
+
magnus::exception::standard_error(),
|
314
334
|
format!("Errno {} while trying to dup {}", errno, fd),
|
315
335
|
)
|
316
336
|
})?;
|
317
337
|
Self::clear_cloexec(dupped_fd).map_err(|e| {
|
318
338
|
magnus::Error::new(
|
319
|
-
magnus::exception::
|
339
|
+
magnus::exception::standard_error(),
|
320
340
|
format!("Failed to clear cloexec flag for fd {}: {}", dupped_fd, e),
|
321
341
|
)
|
322
342
|
})?;
|
@@ -339,7 +359,7 @@ impl ItsiServerConfig {
|
|
339
359
|
serde_json::to_string(&self.server_params.read().listener_info.lock().clone())
|
340
360
|
.map_err(|e| {
|
341
361
|
magnus::Error::new(
|
342
|
-
magnus::exception::
|
362
|
+
magnus::exception::standard_error(),
|
343
363
|
format!("Invalid listener info: {}", e),
|
344
364
|
)
|
345
365
|
})?;
|
@@ -1,6 +1,7 @@
|
|
1
1
|
use crate::server::{
|
2
|
+
lifecycle_event::LifecycleEvent,
|
2
3
|
serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode, ServeStrategy},
|
3
|
-
signal::{clear_signal_handlers, reset_signal_handlers,
|
4
|
+
signal::{clear_signal_handlers, reset_signal_handlers, send_lifecycle_event},
|
4
5
|
};
|
5
6
|
use itsi_rb_helpers::{call_without_gvl, print_rb_backtrace};
|
6
7
|
use itsi_server_config::ItsiServerConfig;
|
@@ -35,7 +36,7 @@ impl ItsiServer {
|
|
35
36
|
}
|
36
37
|
|
37
38
|
pub fn stop(&self) -> Result<()> {
|
38
|
-
|
39
|
+
send_lifecycle_event(LifecycleEvent::Shutdown);
|
39
40
|
Ok(())
|
40
41
|
}
|
41
42
|
|
@@ -44,7 +45,7 @@ impl ItsiServer {
|
|
44
45
|
let result = if self.config.lock().server_params.read().silence {
|
45
46
|
run_silently(|| self.build_and_run_strategy())
|
46
47
|
} else {
|
47
|
-
info!("Itsi - Rolling into action.
|
48
|
+
info!("Itsi - Rolling into action. ⚪💨");
|
48
49
|
self.build_and_run_strategy()
|
49
50
|
};
|
50
51
|
if let Err(e) = result {
|
@@ -1,9 +1,8 @@
|
|
1
1
|
use magnus::{value::Lazy, Module, RClass, RModule};
|
2
2
|
|
3
3
|
pub mod itsi_body_proxy;
|
4
|
-
pub mod
|
5
|
-
pub mod
|
6
|
-
pub mod itsi_grpc_stream;
|
4
|
+
pub mod itsi_grpc_call;
|
5
|
+
pub mod itsi_grpc_response_stream;
|
7
6
|
pub mod itsi_http_request;
|
8
7
|
pub mod itsi_http_response;
|
9
8
|
pub mod itsi_server;
|
@@ -36,20 +35,14 @@ pub static ITSI_BODY_PROXY: Lazy<RClass> = Lazy::new(|ruby| {
|
|
36
35
|
.unwrap()
|
37
36
|
});
|
38
37
|
|
39
|
-
pub static
|
38
|
+
pub static ITSI_GRPC_CALL: Lazy<RClass> = Lazy::new(|ruby| {
|
40
39
|
ruby.get_inner(&ITSI_MODULE)
|
41
|
-
.define_class("
|
40
|
+
.define_class("GrpcCall", ruby.class_object())
|
42
41
|
.unwrap()
|
43
42
|
});
|
44
43
|
|
45
|
-
pub static
|
44
|
+
pub static ITSI_GRPC_RESPONSE_STREAM: Lazy<RClass> = Lazy::new(|ruby| {
|
46
45
|
ruby.get_inner(&ITSI_MODULE)
|
47
|
-
.define_class("
|
48
|
-
.unwrap()
|
49
|
-
});
|
50
|
-
|
51
|
-
pub static ITSI_GRPC_RESPONSE: Lazy<RClass> = Lazy::new(|ruby| {
|
52
|
-
ruby.get_inner(&ITSI_MODULE)
|
53
|
-
.define_class("GrpcResponse", ruby.class_object())
|
46
|
+
.define_class("GrpcResponseStream", ruby.class_object())
|
54
47
|
.unwrap()
|
55
48
|
});
|
@@ -2,6 +2,7 @@ use super::{
|
|
2
2
|
bind_protocol::BindProtocol,
|
3
3
|
tls::{configure_tls, ItsiTlsAcceptor},
|
4
4
|
};
|
5
|
+
use crate::prelude::*;
|
5
6
|
use itsi_error::ItsiError;
|
6
7
|
use std::{
|
7
8
|
collections::HashMap,
|
@@ -28,7 +29,19 @@ pub struct Bind {
|
|
28
29
|
pub address: BindAddress,
|
29
30
|
pub port: Option<u16>, // None for Unix Sockets
|
30
31
|
pub protocol: BindProtocol,
|
31
|
-
pub tls_config: Option<
|
32
|
+
pub tls_config: Option<TlsOptions>,
|
33
|
+
}
|
34
|
+
|
35
|
+
#[derive(Default, Clone)]
|
36
|
+
pub struct TlsOptions {
|
37
|
+
pub host: String,
|
38
|
+
pub options: HashMap<String, String>,
|
39
|
+
}
|
40
|
+
|
41
|
+
impl TlsOptions {
|
42
|
+
pub fn build_acceptor(&self) -> Result<ItsiTlsAcceptor> {
|
43
|
+
configure_tls(&self.host, &self.options)
|
44
|
+
}
|
32
45
|
}
|
33
46
|
|
34
47
|
impl Bind {
|
@@ -73,7 +86,7 @@ impl std::fmt::Debug for Bind {
|
|
73
86
|
impl FromStr for Bind {
|
74
87
|
type Err = ItsiError;
|
75
88
|
|
76
|
-
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
89
|
+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
77
90
|
let (protocol, remainder) = if let Some((proto, rest)) = s.split_once("://") {
|
78
91
|
(proto.parse::<BindProtocol>()?, rest)
|
79
92
|
} else {
|
@@ -138,9 +151,15 @@ impl FromStr for Bind {
|
|
138
151
|
|
139
152
|
let tls_config = match protocol {
|
140
153
|
BindProtocol::Http => None,
|
141
|
-
BindProtocol::Https => Some(
|
154
|
+
BindProtocol::Https => Some(TlsOptions {
|
155
|
+
host: host.to_owned(),
|
156
|
+
options,
|
157
|
+
}),
|
142
158
|
BindProtocol::Unix => None,
|
143
|
-
BindProtocol::Unixs => Some(
|
159
|
+
BindProtocol::Unixs => Some(TlsOptions {
|
160
|
+
host: host.to_owned(),
|
161
|
+
options,
|
162
|
+
}),
|
144
163
|
};
|
145
164
|
let bind = Self {
|
146
165
|
address,
|
@@ -1,7 +1,10 @@
|
|
1
|
+
use crate::prelude::*;
|
2
|
+
use crate::server::io_stream::IoStream;
|
3
|
+
use crate::server::serve_strategy::single_mode::RunningPhase;
|
4
|
+
|
1
5
|
use super::bind::{Bind, BindAddress};
|
2
6
|
use super::bind_protocol::BindProtocol;
|
3
|
-
|
4
|
-
use super::serve_strategy::single_mode::RunningPhase;
|
7
|
+
|
5
8
|
use super::tls::ItsiTlsAcceptor;
|
6
9
|
use itsi_error::{ItsiError, Result};
|
7
10
|
use itsi_tracing::info;
|
@@ -17,7 +20,6 @@ use tokio::net::{unix, TcpStream, UnixStream};
|
|
17
20
|
use tokio::sync::watch::Receiver;
|
18
21
|
use tokio_rustls::TlsAcceptor;
|
19
22
|
use tokio_stream::StreamExt;
|
20
|
-
use tracing::error;
|
21
23
|
|
22
24
|
pub(crate) enum Listener {
|
23
25
|
Tcp(TcpListener),
|
@@ -143,7 +145,7 @@ impl TokioListener {
|
|
143
145
|
ItsiTlsAcceptor::Automatic(acme_acceptor, _, rustls_config) => {
|
144
146
|
let accept_future = acme_acceptor.accept(tcp_stream.0);
|
145
147
|
match accept_future.await {
|
146
|
-
Ok(None) => Err(ItsiError::Pass
|
148
|
+
Ok(None) => Err(ItsiError::Pass),
|
147
149
|
Ok(Some(start_handshake)) => {
|
148
150
|
let tls_stream = start_handshake.into_stream(rustls_config.clone()).await?;
|
149
151
|
Ok(IoStream::TcpTls {
|
@@ -153,7 +155,7 @@ impl TokioListener {
|
|
153
155
|
}
|
154
156
|
Err(error) => {
|
155
157
|
error!(error = format!("{:?}", error));
|
156
|
-
Err(ItsiError::Pass
|
158
|
+
Err(ItsiError::Pass)
|
157
159
|
}
|
158
160
|
}
|
159
161
|
}
|
@@ -330,12 +332,18 @@ impl Listener {
|
|
330
332
|
BindProtocol::Http => Listener::Tcp(revive_tcp_socket(fd)?),
|
331
333
|
BindProtocol::Https => {
|
332
334
|
let tcp_listener = revive_tcp_socket(fd)?;
|
333
|
-
Listener::TcpTls((
|
335
|
+
Listener::TcpTls((
|
336
|
+
tcp_listener,
|
337
|
+
bind.tls_config.unwrap().build_acceptor().unwrap(),
|
338
|
+
))
|
334
339
|
}
|
335
340
|
_ => unreachable!(),
|
336
341
|
},
|
337
342
|
BindAddress::UnixSocket(_) => match bind.tls_config {
|
338
|
-
Some(tls_config) => Listener::UnixTls((
|
343
|
+
Some(tls_config) => Listener::UnixTls((
|
344
|
+
revive_unix_socket(fd)?,
|
345
|
+
tls_config.build_acceptor().unwrap(),
|
346
|
+
)),
|
339
347
|
None => Listener::Unix(revive_unix_socket(fd)?),
|
340
348
|
},
|
341
349
|
};
|
@@ -352,12 +360,18 @@ impl TryFrom<Bind> for Listener {
|
|
352
360
|
BindProtocol::Http => Listener::Tcp(connect_tcp_socket(addr, bind.port.unwrap())?),
|
353
361
|
BindProtocol::Https => {
|
354
362
|
let tcp_listener = connect_tcp_socket(addr, bind.port.unwrap())?;
|
355
|
-
Listener::TcpTls((
|
363
|
+
Listener::TcpTls((
|
364
|
+
tcp_listener,
|
365
|
+
bind.tls_config.unwrap().build_acceptor().unwrap(),
|
366
|
+
))
|
356
367
|
}
|
357
368
|
_ => unreachable!(),
|
358
369
|
},
|
359
370
|
BindAddress::UnixSocket(path) => match bind.tls_config {
|
360
|
-
Some(tls_config) => Listener::UnixTls((
|
371
|
+
Some(tls_config) => Listener::UnixTls((
|
372
|
+
connect_unix_socket(&path)?,
|
373
|
+
tls_config.build_acceptor().unwrap(),
|
374
|
+
)),
|
361
375
|
None => Listener::Unix(connect_unix_socket(&path)?),
|
362
376
|
},
|
363
377
|
};
|
@@ -393,8 +407,8 @@ fn connect_tcp_socket(addr: IpAddr, port: u16) -> Result<TcpListener> {
|
|
393
407
|
};
|
394
408
|
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
|
395
409
|
let socket_address: SocketAddr = SocketAddr::new(addr, port);
|
396
|
-
socket.set_reuse_port(true).ok();
|
397
410
|
socket.set_reuse_address(true).ok();
|
411
|
+
socket.set_reuse_port(true).ok();
|
398
412
|
socket.set_nonblocking(true).ok();
|
399
413
|
socket.set_nodelay(true).ok();
|
400
414
|
socket.set_recv_buffer_size(262_144).ok();
|