itsi-server 0.1.1 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +3937 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +141 -46
- data/ext/itsi_error/Cargo.toml +3 -0
- data/ext/itsi_error/src/lib.rs +98 -24
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +72 -14
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/default_responses/html/401.html +68 -0
- data/ext/itsi_server/src/default_responses/html/403.html +68 -0
- data/ext/itsi_server/src/default_responses/html/404.html +68 -0
- data/ext/itsi_server/src/default_responses/html/413.html +71 -0
- data/ext/itsi_server/src/default_responses/html/429.html +68 -0
- data/ext/itsi_server/src/default_responses/html/500.html +71 -0
- data/ext/itsi_server/src/default_responses/html/502.html +71 -0
- data/ext/itsi_server/src/default_responses/html/503.html +68 -0
- data/ext/itsi_server/src/default_responses/html/504.html +69 -0
- data/ext/itsi_server/src/default_responses/html/index.html +238 -0
- data/ext/itsi_server/src/default_responses/json/401.json +6 -0
- data/ext/itsi_server/src/default_responses/json/403.json +6 -0
- data/ext/itsi_server/src/default_responses/json/404.json +6 -0
- data/ext/itsi_server/src/default_responses/json/413.json +6 -0
- data/ext/itsi_server/src/default_responses/json/429.json +6 -0
- data/ext/itsi_server/src/default_responses/json/500.json +6 -0
- data/ext/itsi_server/src/default_responses/json/502.json +6 -0
- data/ext/itsi_server/src/default_responses/json/503.json +6 -0
- data/ext/itsi_server/src/default_responses/json/504.json +6 -0
- data/ext/itsi_server/src/default_responses/mod.rs +11 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +132 -40
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +143 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +345 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +391 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +375 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +83 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
- data/ext/itsi_server/src/server/binds/bind.rs +201 -0
- data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/binds/listener.rs +432 -0
- data/ext/itsi_server/src/server/binds/mod.rs +4 -0
- data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/binds/tls.rs +270 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/http_message_types.rs +97 -0
- data/ext/itsi_server/src/server/io_stream.rs +105 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +165 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +56 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +87 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +86 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +285 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +142 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +289 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +292 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +157 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +201 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +87 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +414 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +131 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +44 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +36 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +180 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +163 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +347 -0
- data/ext/itsi_server/src/server/mod.rs +12 -5
- data/ext/itsi_server/src/server/process_worker.rs +247 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +342 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +76 -0
- data/ext/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/ext/itsi_server/src/server/thread_worker.rs +475 -0
- data/ext/itsi_server/src/services/cache_store.rs +74 -0
- data/ext/itsi_server/src/services/itsi_http_service.rs +239 -0
- data/ext/itsi_server/src/services/mime_types.rs +1416 -0
- data/ext/itsi_server/src/services/mod.rs +6 -0
- data/ext/itsi_server/src/services/password_hasher.rs +83 -0
- data/ext/itsi_server/src/services/rate_limiter.rs +569 -0
- data/ext/itsi_server/src/services/static_file_server.rs +1324 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +315 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
- data/lib/itsi/http_request.rb +186 -0
- data/lib/itsi/http_response.rb +41 -0
- data/lib/itsi/passfile.rb +109 -0
- data/lib/itsi/server/config/dsl.rb +565 -0
- data/lib/itsi/server/config.rb +166 -0
- data/lib/itsi/server/default_app/default_app.rb +34 -0
- data/lib/itsi/server/default_app/index.html +115 -0
- data/lib/itsi/server/default_config/Itsi-rackup.rb +119 -0
- data/lib/itsi/server/default_config/Itsi.rb +107 -0
- data/lib/itsi/server/grpc/grpc_call.rb +246 -0
- data/lib/itsi/server/grpc/grpc_interface.rb +100 -0
- data/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/route_tester.rb +107 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
- data/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
- data/lib/itsi/server/typed_handlers.rb +17 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +160 -9
- data/lib/itsi/standard_headers.rb +86 -0
- data/lib/ruby_lsp/itsi/addon.rb +111 -0
- data/lib/shell_completions/completions.rb +26 -0
- metadata +182 -25
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/bind.rs +0 -138
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/listener.rs +0 -218
- data/ext/itsi_server/src/server/tls.rs +0 -138
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,375 @@
|
|
1
|
+
use super::file_watcher::{self};
|
2
|
+
use crate::{
|
3
|
+
ruby_types::ITSI_SERVER_CONFIG,
|
4
|
+
server::{
|
5
|
+
binds::{bind::Bind, listener::Listener},
|
6
|
+
middleware_stack::MiddlewareSet,
|
7
|
+
},
|
8
|
+
};
|
9
|
+
use derive_more::Debug;
|
10
|
+
use itsi_rb_helpers::{call_with_gvl, print_rb_backtrace, HeapValue};
|
11
|
+
use itsi_tracing::{set_format, set_level, set_target};
|
12
|
+
use magnus::{
|
13
|
+
block::Proc,
|
14
|
+
error::Result,
|
15
|
+
value::{LazyId, ReprValue},
|
16
|
+
RArray, RHash, Ruby, Symbol, Value,
|
17
|
+
};
|
18
|
+
use nix::{
|
19
|
+
fcntl::{fcntl, FcntlArg, FdFlag},
|
20
|
+
unistd::{close, dup},
|
21
|
+
};
|
22
|
+
use parking_lot::{Mutex, RwLock};
|
23
|
+
use std::{
|
24
|
+
collections::HashMap,
|
25
|
+
os::fd::{AsRawFd, OwnedFd, RawFd},
|
26
|
+
path::PathBuf,
|
27
|
+
sync::{Arc, OnceLock},
|
28
|
+
time::Duration,
|
29
|
+
};
|
30
|
+
static DEFAULT_BIND: &str = "http://localhost:3000";
|
31
|
+
static ID_BUILD_CONFIG: LazyId = LazyId::new("build_config");
|
32
|
+
static ID_RELOAD_EXEC: LazyId = LazyId::new("reload_exec");
|
33
|
+
|
34
|
+
#[derive(Debug, Clone)]
|
35
|
+
pub struct ItsiServerConfig {
|
36
|
+
pub cli_params: Arc<HeapValue<RHash>>,
|
37
|
+
pub itsifile_path: Option<PathBuf>,
|
38
|
+
pub itsi_config_proc: Arc<Option<HeapValue<Proc>>>,
|
39
|
+
#[debug(skip)]
|
40
|
+
pub server_params: Arc<RwLock<Arc<ServerParams>>>,
|
41
|
+
pub watcher_fd: Arc<Option<OwnedFd>>,
|
42
|
+
}
|
43
|
+
|
44
|
+
#[derive(Debug)]
|
45
|
+
pub struct ServerParams {
|
46
|
+
/// Cluster params
|
47
|
+
pub workers: u8,
|
48
|
+
pub worker_memory_limit: Option<u64>,
|
49
|
+
pub silence: bool,
|
50
|
+
pub shutdown_timeout: f64,
|
51
|
+
pub hooks: HashMap<String, HeapValue<Proc>>,
|
52
|
+
pub preload: bool,
|
53
|
+
|
54
|
+
pub request_timeout: Option<Duration>,
|
55
|
+
pub notify_watchers: Option<Vec<(String, Vec<Vec<String>>)>>,
|
56
|
+
/// Worker params
|
57
|
+
pub threads: u8,
|
58
|
+
pub scheduler_threads: Option<u8>,
|
59
|
+
pub streamable_body: bool,
|
60
|
+
pub multithreaded_reactor: bool,
|
61
|
+
pub pin_worker_cores: bool,
|
62
|
+
pub scheduler_class: Option<String>,
|
63
|
+
pub oob_gc_responses_threshold: Option<u64>,
|
64
|
+
pub middleware_loader: HeapValue<Proc>,
|
65
|
+
pub middleware: OnceLock<MiddlewareSet>,
|
66
|
+
pub binds: Vec<Bind>,
|
67
|
+
#[debug(skip)]
|
68
|
+
pub(crate) listeners: Mutex<Vec<Listener>>,
|
69
|
+
listener_info: Mutex<HashMap<String, i32>>,
|
70
|
+
}
|
71
|
+
|
72
|
+
impl ServerParams {
|
73
|
+
pub fn preload_ruby(self: &Arc<Self>) -> Result<()> {
|
74
|
+
call_with_gvl(|ruby| -> Result<()> {
|
75
|
+
if self
|
76
|
+
.scheduler_class
|
77
|
+
.as_ref()
|
78
|
+
.is_some_and(|t| t == "Itsi::Scheduler")
|
79
|
+
{
|
80
|
+
ruby.require("itsi/scheduler")?;
|
81
|
+
}
|
82
|
+
let middleware = MiddlewareSet::new(
|
83
|
+
self.middleware_loader
|
84
|
+
.call::<_, Option<Value>>(())
|
85
|
+
.inspect_err(|e| {
|
86
|
+
if let Some(err_value) = e.value() {
|
87
|
+
print_rb_backtrace(err_value);
|
88
|
+
}
|
89
|
+
})?
|
90
|
+
.map(|mw| mw.into()),
|
91
|
+
)?;
|
92
|
+
self.middleware.set(middleware).map_err(|_| {
|
93
|
+
magnus::Error::new(
|
94
|
+
magnus::exception::runtime_error(),
|
95
|
+
"Failed to set middleware",
|
96
|
+
)
|
97
|
+
})?;
|
98
|
+
Ok(())
|
99
|
+
})?;
|
100
|
+
Ok(())
|
101
|
+
}
|
102
|
+
|
103
|
+
fn from_rb_hash(rb_param_hash: RHash) -> Result<ServerParams> {
|
104
|
+
let workers = rb_param_hash
|
105
|
+
.fetch::<_, Option<u8>>("workers")?
|
106
|
+
.unwrap_or(num_cpus::get() as u8);
|
107
|
+
let worker_memory_limit: Option<u64> = rb_param_hash.fetch("worker_memory_limit")?;
|
108
|
+
let silence: bool = rb_param_hash.fetch("silence")?;
|
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);
|
115
|
+
let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?;
|
116
|
+
|
117
|
+
let hooks: Option<RHash> = rb_param_hash.fetch("hooks")?;
|
118
|
+
let hooks = hooks
|
119
|
+
.map(|rhash| -> Result<HashMap<String, HeapValue<Proc>>> {
|
120
|
+
let mut hook_map: HashMap<String, HeapValue<Proc>> = HashMap::new();
|
121
|
+
for pair in rhash.enumeratorize::<_, ()>("each", ()) {
|
122
|
+
if let Some(pair_value) = RArray::from_value(pair?) {
|
123
|
+
if let (Ok(key), Ok(value)) =
|
124
|
+
(pair_value.entry::<Value>(0), pair_value.entry::<Proc>(1))
|
125
|
+
{
|
126
|
+
hook_map.insert(key.to_string(), HeapValue::from(value));
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
Ok(hook_map)
|
131
|
+
})
|
132
|
+
.transpose()?
|
133
|
+
.unwrap_or_default();
|
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
|
+
|
138
|
+
let notify_watchers: Option<Vec<(String, Vec<Vec<String>>)>> =
|
139
|
+
rb_param_hash.fetch("notify_watchers")?;
|
140
|
+
let threads: u8 = rb_param_hash.fetch("threads")?;
|
141
|
+
let scheduler_threads: Option<u8> = rb_param_hash.fetch("scheduler_threads")?;
|
142
|
+
let streamable_body: bool = rb_param_hash.fetch("streamable_body")?;
|
143
|
+
let scheduler_class: Option<String> = rb_param_hash.fetch("scheduler_class")?;
|
144
|
+
let oob_gc_responses_threshold: Option<u64> =
|
145
|
+
rb_param_hash.fetch("oob_gc_responses_threshold")?;
|
146
|
+
let middleware_loader: Proc = rb_param_hash.fetch("middleware_loader")?;
|
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")?;
|
150
|
+
|
151
|
+
if let Some(level) = log_level {
|
152
|
+
set_level(&level);
|
153
|
+
}
|
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
|
+
|
163
|
+
let binds: Option<Vec<String>> = rb_param_hash.fetch("binds")?;
|
164
|
+
let binds = binds
|
165
|
+
.unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
|
166
|
+
.into_iter()
|
167
|
+
.map(|s| s.parse())
|
168
|
+
.collect::<itsi_error::Result<Vec<Bind>>>()?;
|
169
|
+
|
170
|
+
let listeners = if let Some(preexisting_listeners) =
|
171
|
+
rb_param_hash.delete::<_, Option<String>>("listeners")?
|
172
|
+
{
|
173
|
+
let bind_to_fd_map: HashMap<String, i32> = serde_json::from_str(&preexisting_listeners)
|
174
|
+
.map_err(|e| {
|
175
|
+
magnus::Error::new(
|
176
|
+
magnus::exception::standard_error(),
|
177
|
+
format!("Invalid listener info: {}", e),
|
178
|
+
)
|
179
|
+
})?;
|
180
|
+
|
181
|
+
binds
|
182
|
+
.iter()
|
183
|
+
.cloned()
|
184
|
+
.map(|bind| {
|
185
|
+
if let Some(fd) = bind_to_fd_map.get(&bind.listener_address_string()) {
|
186
|
+
Listener::inherit_fd(bind, *fd)
|
187
|
+
} else {
|
188
|
+
Listener::try_from(bind)
|
189
|
+
}
|
190
|
+
})
|
191
|
+
.collect::<std::result::Result<Vec<Listener>, _>>()?
|
192
|
+
.into_iter()
|
193
|
+
.collect::<Vec<_>>()
|
194
|
+
} else {
|
195
|
+
binds
|
196
|
+
.iter()
|
197
|
+
.cloned()
|
198
|
+
.map(Listener::try_from)
|
199
|
+
.collect::<std::result::Result<Vec<Listener>, _>>()?
|
200
|
+
.into_iter()
|
201
|
+
.collect::<Vec<_>>()
|
202
|
+
};
|
203
|
+
|
204
|
+
let listener_info = listeners
|
205
|
+
.iter()
|
206
|
+
.map(|listener| {
|
207
|
+
listener.handover().map_err(|e| {
|
208
|
+
magnus::Error::new(magnus::exception::runtime_error(), e.to_string())
|
209
|
+
})
|
210
|
+
})
|
211
|
+
.collect::<Result<HashMap<String, i32>>>()?;
|
212
|
+
|
213
|
+
Ok(ServerParams {
|
214
|
+
workers,
|
215
|
+
worker_memory_limit,
|
216
|
+
silence,
|
217
|
+
multithreaded_reactor,
|
218
|
+
pin_worker_cores,
|
219
|
+
shutdown_timeout,
|
220
|
+
hooks,
|
221
|
+
preload,
|
222
|
+
request_timeout,
|
223
|
+
notify_watchers,
|
224
|
+
threads,
|
225
|
+
scheduler_threads,
|
226
|
+
streamable_body,
|
227
|
+
scheduler_class,
|
228
|
+
oob_gc_responses_threshold,
|
229
|
+
binds,
|
230
|
+
listener_info: Mutex::new(listener_info),
|
231
|
+
listeners: Mutex::new(listeners),
|
232
|
+
middleware_loader: middleware_loader.into(),
|
233
|
+
middleware: OnceLock::new(),
|
234
|
+
})
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
impl ItsiServerConfig {
|
239
|
+
pub fn new(
|
240
|
+
ruby: &Ruby,
|
241
|
+
cli_params: RHash,
|
242
|
+
itsifile_path: Option<PathBuf>,
|
243
|
+
itsi_config_proc: Option<Proc>,
|
244
|
+
) -> Result<Self> {
|
245
|
+
let itsi_config_proc = Arc::new(itsi_config_proc.map(HeapValue::from));
|
246
|
+
let server_params = Self::combine_params(
|
247
|
+
ruby,
|
248
|
+
cli_params,
|
249
|
+
itsifile_path.as_ref(),
|
250
|
+
itsi_config_proc.clone(),
|
251
|
+
)?;
|
252
|
+
cli_params.delete::<_, Value>(Symbol::new("listeners"))?;
|
253
|
+
|
254
|
+
let watcher_fd = if let Some(watchers) = server_params.notify_watchers.clone() {
|
255
|
+
file_watcher::watch_groups(watchers)?
|
256
|
+
} else {
|
257
|
+
None
|
258
|
+
};
|
259
|
+
|
260
|
+
Ok(ItsiServerConfig {
|
261
|
+
cli_params: Arc::new(cli_params.into()),
|
262
|
+
server_params: RwLock::new(server_params.clone()).into(),
|
263
|
+
itsi_config_proc,
|
264
|
+
itsifile_path,
|
265
|
+
watcher_fd: watcher_fd.into(),
|
266
|
+
})
|
267
|
+
}
|
268
|
+
|
269
|
+
/// Reload
|
270
|
+
pub fn reload(self: Arc<Self>, cluster_worker: bool) -> Result<bool> {
|
271
|
+
let server_params = call_with_gvl(|ruby| {
|
272
|
+
Self::combine_params(
|
273
|
+
&ruby,
|
274
|
+
self.cli_params.cloned(),
|
275
|
+
self.itsifile_path.as_ref(),
|
276
|
+
self.itsi_config_proc.clone(),
|
277
|
+
)
|
278
|
+
})?;
|
279
|
+
|
280
|
+
let is_single_mode = self.server_params.read().workers == 1;
|
281
|
+
|
282
|
+
let requires_exec = if !is_single_mode && !server_params.preload {
|
283
|
+
// In cluster mode children are cycled during a reload
|
284
|
+
// and if preload is disabled, will get a clean memory slate,
|
285
|
+
// so we don't need to exec.
|
286
|
+
false
|
287
|
+
} else {
|
288
|
+
// In non-cluster mode, or when preloading is enabled, we shouldn't try to
|
289
|
+
// reload inside the existing process (as new code may conflict with old),
|
290
|
+
// and should re-exec instead.
|
291
|
+
true
|
292
|
+
};
|
293
|
+
|
294
|
+
*self.server_params.write() = server_params.clone();
|
295
|
+
Ok(requires_exec && (cluster_worker || is_single_mode))
|
296
|
+
}
|
297
|
+
|
298
|
+
fn combine_params(
|
299
|
+
ruby: &Ruby,
|
300
|
+
cli_params: RHash,
|
301
|
+
itsifile_path: Option<&PathBuf>,
|
302
|
+
itsi_config_proc: Arc<Option<HeapValue<Proc>>>,
|
303
|
+
) -> Result<Arc<ServerParams>> {
|
304
|
+
let inner = itsi_config_proc
|
305
|
+
.as_ref()
|
306
|
+
.clone()
|
307
|
+
.map(|hv| hv.clone().inner());
|
308
|
+
let rb_param_hash: RHash = ruby.get_inner_ref(&ITSI_SERVER_CONFIG).funcall(
|
309
|
+
*ID_BUILD_CONFIG,
|
310
|
+
(cli_params, itsifile_path.cloned(), inner),
|
311
|
+
)?;
|
312
|
+
Ok(Arc::new(ServerParams::from_rb_hash(rb_param_hash)?))
|
313
|
+
}
|
314
|
+
|
315
|
+
fn clear_cloexec(fd: RawFd) -> nix::Result<()> {
|
316
|
+
let current_flags = fcntl(fd, FcntlArg::F_GETFD)?;
|
317
|
+
let mut flags = FdFlag::from_bits_truncate(current_flags);
|
318
|
+
// Remove the FD_CLOEXEC flag
|
319
|
+
flags.remove(FdFlag::FD_CLOEXEC);
|
320
|
+
// Set the new flags back on the file descriptor
|
321
|
+
fcntl(fd, FcntlArg::F_SETFD(flags))?;
|
322
|
+
Ok(())
|
323
|
+
}
|
324
|
+
|
325
|
+
pub fn dup_fds(self: &Arc<Self>) -> Result<()> {
|
326
|
+
let binding = self.server_params.read();
|
327
|
+
let mut listener_info_guard = binding.listener_info.lock();
|
328
|
+
let dupped_fd_map = listener_info_guard
|
329
|
+
.iter()
|
330
|
+
.map(|(str, fd)| {
|
331
|
+
let dupped_fd = dup(*fd).map_err(|errno| {
|
332
|
+
magnus::Error::new(
|
333
|
+
magnus::exception::standard_error(),
|
334
|
+
format!("Errno {} while trying to dup {}", errno, fd),
|
335
|
+
)
|
336
|
+
})?;
|
337
|
+
Self::clear_cloexec(dupped_fd).map_err(|e| {
|
338
|
+
magnus::Error::new(
|
339
|
+
magnus::exception::standard_error(),
|
340
|
+
format!("Failed to clear cloexec flag for fd {}: {}", dupped_fd, e),
|
341
|
+
)
|
342
|
+
})?;
|
343
|
+
Ok((str.clone(), dupped_fd))
|
344
|
+
})
|
345
|
+
.collect::<Result<HashMap<String, i32>>>()?;
|
346
|
+
*listener_info_guard = dupped_fd_map;
|
347
|
+
Ok(())
|
348
|
+
}
|
349
|
+
|
350
|
+
pub fn stop_watcher(self: &Arc<Self>) -> Result<()> {
|
351
|
+
if let Some(r_fd) = self.watcher_fd.as_ref() {
|
352
|
+
close(r_fd.as_raw_fd()).ok();
|
353
|
+
}
|
354
|
+
Ok(())
|
355
|
+
}
|
356
|
+
|
357
|
+
pub fn reload_exec(self: &Arc<Self>) -> Result<()> {
|
358
|
+
let listener_json =
|
359
|
+
serde_json::to_string(&self.server_params.read().listener_info.lock().clone())
|
360
|
+
.map_err(|e| {
|
361
|
+
magnus::Error::new(
|
362
|
+
magnus::exception::standard_error(),
|
363
|
+
format!("Invalid listener info: {}", e),
|
364
|
+
)
|
365
|
+
})?;
|
366
|
+
|
367
|
+
self.stop_watcher()?;
|
368
|
+
call_with_gvl(|ruby| -> Result<()> {
|
369
|
+
ruby.get_inner_ref(&ITSI_SERVER_CONFIG)
|
370
|
+
.funcall::<_, _, Value>(*ID_RELOAD_EXEC, (listener_json,))?;
|
371
|
+
Ok(())
|
372
|
+
})?;
|
373
|
+
Ok(())
|
374
|
+
}
|
375
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
use crate::server::{
|
2
|
+
lifecycle_event::LifecycleEvent,
|
3
|
+
serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode, ServeStrategy},
|
4
|
+
signal::{clear_signal_handlers, reset_signal_handlers, send_lifecycle_event},
|
5
|
+
};
|
6
|
+
use itsi_rb_helpers::{call_without_gvl, print_rb_backtrace};
|
7
|
+
use itsi_server_config::ItsiServerConfig;
|
8
|
+
use itsi_tracing::{error, run_silently};
|
9
|
+
use magnus::{block::Proc, error::Result, RHash, Ruby};
|
10
|
+
use parking_lot::Mutex;
|
11
|
+
use std::{path::PathBuf, sync::Arc};
|
12
|
+
use tracing::{info, instrument};
|
13
|
+
mod file_watcher;
|
14
|
+
pub mod itsi_server_config;
|
15
|
+
#[magnus::wrap(class = "Itsi::Server", free_immediately, size)]
|
16
|
+
#[derive(Clone)]
|
17
|
+
pub struct ItsiServer {
|
18
|
+
pub config: Arc<Mutex<Arc<ItsiServerConfig>>>,
|
19
|
+
}
|
20
|
+
|
21
|
+
impl ItsiServer {
|
22
|
+
pub fn new(
|
23
|
+
ruby: &Ruby,
|
24
|
+
cli_params: RHash,
|
25
|
+
itsifile_path: Option<PathBuf>,
|
26
|
+
itsi_config_proc: Option<Proc>,
|
27
|
+
) -> Result<Self> {
|
28
|
+
Ok(Self {
|
29
|
+
config: Arc::new(Mutex::new(Arc::new(ItsiServerConfig::new(
|
30
|
+
ruby,
|
31
|
+
cli_params,
|
32
|
+
itsifile_path,
|
33
|
+
itsi_config_proc,
|
34
|
+
)?))),
|
35
|
+
})
|
36
|
+
}
|
37
|
+
|
38
|
+
pub fn stop(&self) -> Result<()> {
|
39
|
+
send_lifecycle_event(LifecycleEvent::Shutdown);
|
40
|
+
Ok(())
|
41
|
+
}
|
42
|
+
|
43
|
+
#[instrument(skip(self))]
|
44
|
+
pub fn start(&self) -> Result<()> {
|
45
|
+
let result = if self.config.lock().server_params.read().silence {
|
46
|
+
run_silently(|| self.build_and_run_strategy())
|
47
|
+
} else {
|
48
|
+
info!("Itsi - Rolling into action. ⚪💨");
|
49
|
+
self.build_and_run_strategy()
|
50
|
+
};
|
51
|
+
if let Err(e) = result {
|
52
|
+
if let Some(err_value) = e.value() {
|
53
|
+
print_rb_backtrace(err_value);
|
54
|
+
}
|
55
|
+
return Err(e);
|
56
|
+
}
|
57
|
+
Ok(())
|
58
|
+
}
|
59
|
+
|
60
|
+
pub(crate) fn build_strategy(&self) -> Result<ServeStrategy> {
|
61
|
+
let server_config = self.config.lock();
|
62
|
+
Ok(if server_config.server_params.read().workers > 1 {
|
63
|
+
ServeStrategy::Cluster(Arc::new(ClusterMode::new(server_config.clone())))
|
64
|
+
} else {
|
65
|
+
ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone())?))
|
66
|
+
})
|
67
|
+
}
|
68
|
+
|
69
|
+
fn build_and_run_strategy(&self) -> Result<()> {
|
70
|
+
reset_signal_handlers();
|
71
|
+
call_without_gvl(move || -> Result<()> {
|
72
|
+
let strategy = self.build_strategy()?;
|
73
|
+
if let Err(e) = strategy.clone().run() {
|
74
|
+
error!("Error running server: {}", e);
|
75
|
+
strategy.stop()?;
|
76
|
+
}
|
77
|
+
Ok(())
|
78
|
+
})?;
|
79
|
+
clear_signal_handlers();
|
80
|
+
info!("Server stopped");
|
81
|
+
Ok(())
|
82
|
+
}
|
83
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
use magnus::{value::Lazy, Module, RClass, RModule};
|
2
|
+
|
3
|
+
pub mod itsi_body_proxy;
|
4
|
+
pub mod itsi_grpc_call;
|
5
|
+
pub mod itsi_grpc_response_stream;
|
6
|
+
pub mod itsi_http_request;
|
7
|
+
pub mod itsi_http_response;
|
8
|
+
pub mod itsi_server;
|
9
|
+
|
10
|
+
pub static ITSI_MODULE: Lazy<RModule> = Lazy::new(|ruby| ruby.define_module("Itsi").unwrap());
|
11
|
+
pub static ITSI_SERVER: Lazy<RClass> = Lazy::new(|ruby| {
|
12
|
+
ruby.get_inner(&ITSI_MODULE)
|
13
|
+
.define_class("Server", ruby.class_object())
|
14
|
+
.unwrap()
|
15
|
+
});
|
16
|
+
|
17
|
+
pub static ITSI_SERVER_CONFIG: Lazy<RModule> =
|
18
|
+
Lazy::new(|ruby| ruby.get_inner(&ITSI_SERVER).const_get("Config").unwrap());
|
19
|
+
|
20
|
+
pub static ITSI_REQUEST: Lazy<RClass> = Lazy::new(|ruby| {
|
21
|
+
ruby.get_inner(&ITSI_MODULE)
|
22
|
+
.define_class("HttpRequest", ruby.class_object())
|
23
|
+
.unwrap()
|
24
|
+
});
|
25
|
+
|
26
|
+
pub static ITSI_RESPONSE: Lazy<RClass> = Lazy::new(|ruby| {
|
27
|
+
ruby.get_inner(&ITSI_MODULE)
|
28
|
+
.define_class("HttpResponse", ruby.class_object())
|
29
|
+
.unwrap()
|
30
|
+
});
|
31
|
+
|
32
|
+
pub static ITSI_BODY_PROXY: Lazy<RClass> = Lazy::new(|ruby| {
|
33
|
+
ruby.get_inner(&ITSI_MODULE)
|
34
|
+
.define_class("BodyProxy", ruby.class_object())
|
35
|
+
.unwrap()
|
36
|
+
});
|
37
|
+
|
38
|
+
pub static ITSI_GRPC_CALL: Lazy<RClass> = Lazy::new(|ruby| {
|
39
|
+
ruby.get_inner(&ITSI_MODULE)
|
40
|
+
.define_class("GrpcCall", ruby.class_object())
|
41
|
+
.unwrap()
|
42
|
+
});
|
43
|
+
|
44
|
+
pub static ITSI_GRPC_RESPONSE_STREAM: Lazy<RClass> = Lazy::new(|ruby| {
|
45
|
+
ruby.get_inner(&ITSI_MODULE)
|
46
|
+
.define_class("GrpcResponseStream", ruby.class_object())
|
47
|
+
.unwrap()
|
48
|
+
});
|
@@ -0,0 +1,201 @@
|
|
1
|
+
use super::{
|
2
|
+
bind_protocol::BindProtocol,
|
3
|
+
tls::{configure_tls, ItsiTlsAcceptor},
|
4
|
+
};
|
5
|
+
use crate::prelude::*;
|
6
|
+
use itsi_error::ItsiError;
|
7
|
+
use std::{
|
8
|
+
collections::HashMap,
|
9
|
+
net::{IpAddr, Ipv4Addr, Ipv6Addr, ToSocketAddrs},
|
10
|
+
path::PathBuf,
|
11
|
+
str::FromStr,
|
12
|
+
};
|
13
|
+
|
14
|
+
#[derive(Debug, Clone)]
|
15
|
+
pub enum BindAddress {
|
16
|
+
Ip(IpAddr),
|
17
|
+
UnixSocket(PathBuf),
|
18
|
+
}
|
19
|
+
|
20
|
+
impl Default for BindAddress {
|
21
|
+
fn default() -> Self {
|
22
|
+
BindAddress::Ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
#[derive(Default, Clone)]
|
27
|
+
#[magnus::wrap(class = "Itsi::Bind")]
|
28
|
+
pub struct Bind {
|
29
|
+
pub address: BindAddress,
|
30
|
+
pub port: Option<u16>, // None for Unix Sockets
|
31
|
+
pub protocol: BindProtocol,
|
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
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
impl Bind {
|
48
|
+
pub fn listener_address_string(&self) -> String {
|
49
|
+
match &self.address {
|
50
|
+
BindAddress::Ip(ip) => format!("tcp://{}:{}", ip.to_canonical(), self.port.unwrap()),
|
51
|
+
BindAddress::UnixSocket(path) => {
|
52
|
+
format!("unix://{}", path.as_path().to_str().unwrap())
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
impl std::fmt::Debug for Bind {
|
59
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
60
|
+
match &self.address {
|
61
|
+
BindAddress::Ip(ip) => match self.protocol {
|
62
|
+
BindProtocol::Https if self.port == Some(443) => {
|
63
|
+
write!(f, "{}://{}", self.protocol, ip)
|
64
|
+
}
|
65
|
+
BindProtocol::Http if self.port == Some(80) => {
|
66
|
+
write!(f, "{}://{}", self.protocol, ip)
|
67
|
+
}
|
68
|
+
_ => match self.port {
|
69
|
+
Some(port) => write!(f, "{}://{}:{}", self.protocol, ip, port),
|
70
|
+
None => write!(f, "{}://{}", self.protocol, ip),
|
71
|
+
},
|
72
|
+
},
|
73
|
+
BindAddress::UnixSocket(path) => {
|
74
|
+
write!(f, "{}://{}", self.protocol, path.display())
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
/// We can build a Bind from a string in the format `protocol://host:port?options`
|
81
|
+
/// E.g.
|
82
|
+
/// *`https://example.com:443?tls_cert=/path/to/cert.pem&tls_key=/path/to/key.pem`
|
83
|
+
/// *`unix:///path/to/socket.sock`
|
84
|
+
/// *`http://example.com:80`
|
85
|
+
/// *`https://[::]:80`
|
86
|
+
impl FromStr for Bind {
|
87
|
+
type Err = ItsiError;
|
88
|
+
|
89
|
+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
90
|
+
let (protocol, remainder) = if let Some((proto, rest)) = s.split_once("://") {
|
91
|
+
(proto.parse::<BindProtocol>()?, rest)
|
92
|
+
} else {
|
93
|
+
(BindProtocol::Https, s)
|
94
|
+
};
|
95
|
+
|
96
|
+
let (url, options) = if let Some((base, options)) = remainder.split_once('?') {
|
97
|
+
(base, parse_bind_options(options))
|
98
|
+
} else {
|
99
|
+
(remainder, HashMap::new())
|
100
|
+
};
|
101
|
+
|
102
|
+
let (host, port) = if url.starts_with('[') {
|
103
|
+
// IPv6 with brackets `[::]:port`
|
104
|
+
if let Some(end) = url.find(']') {
|
105
|
+
let host = &url[1..end]; // Extract `::`
|
106
|
+
let port = url[end + 1..]
|
107
|
+
.strip_prefix(':')
|
108
|
+
.and_then(|p| p.parse().ok());
|
109
|
+
(host, port)
|
110
|
+
} else {
|
111
|
+
return Err(ItsiError::InvalidInput(
|
112
|
+
"Invalid IPv6 address format".to_owned(),
|
113
|
+
));
|
114
|
+
}
|
115
|
+
} else if let Some((h, p)) = url.rsplit_once(':') {
|
116
|
+
// Check if `h` is an IPv6 address before assuming it's a port
|
117
|
+
if h.contains('.') || h.parse::<Ipv4Addr>().is_ok() {
|
118
|
+
(h, p.parse::<u16>().ok()) // IPv4 case
|
119
|
+
} else if h.parse::<Ipv6Addr>().is_ok() {
|
120
|
+
// If it's IPv6, require brackets for port
|
121
|
+
return Err(ItsiError::InvalidInput(
|
122
|
+
"IPv6 addresses must use [ ] when specifying a port".to_owned(),
|
123
|
+
));
|
124
|
+
} else {
|
125
|
+
(h, p.parse::<u16>().ok()) // Treat as a hostname
|
126
|
+
}
|
127
|
+
} else {
|
128
|
+
(url, None)
|
129
|
+
};
|
130
|
+
|
131
|
+
let address = if let Ok(ip) = host.parse::<IpAddr>() {
|
132
|
+
BindAddress::Ip(ip)
|
133
|
+
} else {
|
134
|
+
match protocol {
|
135
|
+
BindProtocol::Https | BindProtocol::Http => resolve_hostname(host)
|
136
|
+
.map(BindAddress::Ip)
|
137
|
+
.ok_or(ItsiError::ArgumentError(format!(
|
138
|
+
"Failed to resolve hostname {}",
|
139
|
+
host
|
140
|
+
)))?,
|
141
|
+
BindProtocol::Unix | BindProtocol::Unixs => BindAddress::UnixSocket(host.into()),
|
142
|
+
}
|
143
|
+
};
|
144
|
+
|
145
|
+
let port = match protocol {
|
146
|
+
BindProtocol::Http => port.or(Some(80)),
|
147
|
+
BindProtocol::Https => port.or(Some(443)),
|
148
|
+
BindProtocol::Unix => None,
|
149
|
+
BindProtocol::Unixs => None,
|
150
|
+
};
|
151
|
+
|
152
|
+
let tls_config = match protocol {
|
153
|
+
BindProtocol::Http => None,
|
154
|
+
BindProtocol::Https => Some(TlsOptions {
|
155
|
+
host: host.to_owned(),
|
156
|
+
options,
|
157
|
+
}),
|
158
|
+
BindProtocol::Unix => None,
|
159
|
+
BindProtocol::Unixs => Some(TlsOptions {
|
160
|
+
host: host.to_owned(),
|
161
|
+
options,
|
162
|
+
}),
|
163
|
+
};
|
164
|
+
let bind = Self {
|
165
|
+
address,
|
166
|
+
port,
|
167
|
+
protocol,
|
168
|
+
tls_config,
|
169
|
+
};
|
170
|
+
Ok(bind)
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
fn parse_bind_options(query: &str) -> HashMap<String, String> {
|
175
|
+
query
|
176
|
+
.split('&')
|
177
|
+
.filter_map(|pair| pair.split_once('='))
|
178
|
+
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
179
|
+
.collect()
|
180
|
+
}
|
181
|
+
|
182
|
+
/// Attempts to resolve a hostname into an IP address.
|
183
|
+
fn resolve_hostname(hostname: &str) -> Option<IpAddr> {
|
184
|
+
(hostname, 0)
|
185
|
+
.to_socket_addrs()
|
186
|
+
.ok()?
|
187
|
+
.find_map(|addr| {
|
188
|
+
if addr.is_ipv4() {
|
189
|
+
Some(addr.ip()) // Prefer IPv4
|
190
|
+
} else {
|
191
|
+
None
|
192
|
+
}
|
193
|
+
})
|
194
|
+
.or_else(|| {
|
195
|
+
(hostname, 0)
|
196
|
+
.to_socket_addrs()
|
197
|
+
.ok()?
|
198
|
+
.map(|addr| addr.ip())
|
199
|
+
.next()
|
200
|
+
}) // Fallback to IPv4
|
201
|
+
}
|