itsi-scheduler 0.1.5 → 0.1.11
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.
Potentially problematic release.
This version of itsi-scheduler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Cargo.lock +12 -12
- data/ext/itsi_error/src/from.rs +26 -29
- data/ext/itsi_error/src/lib.rs +1 -1
- data/ext/itsi_rb_helpers/src/lib.rs +27 -4
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +6 -2
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +75 -1
- data/ext/itsi_server/src/request/itsi_request.rs +39 -18
- data/ext/itsi_server/src/response/itsi_response.rs +14 -4
- data/ext/itsi_server/src/server/bind.rs +20 -15
- data/ext/itsi_server/src/server/itsi_server.rs +147 -103
- data/ext/itsi_server/src/server/listener.rs +99 -108
- data/ext/itsi_server/src/server/process_worker.rs +10 -3
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +15 -9
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +144 -115
- data/ext/itsi_server/src/server/signal.rs +4 -0
- data/ext/itsi_server/src/server/thread_worker.rs +55 -24
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +55 -17
- data/ext/itsi_server/src/server/tls.rs +104 -28
- data/ext/itsi_tracing/src/lib.rs +18 -1
- data/lib/itsi/scheduler/version.rb +1 -1
- metadata +4 -4
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
data/ext/itsi_server/Cargo.toml
CHANGED
@@ -19,11 +19,12 @@ parking_lot = "0.12.3"
|
|
19
19
|
rustls-pemfile = "2.2.0"
|
20
20
|
tokio-rustls = "0.26.2"
|
21
21
|
bytes = "1.3"
|
22
|
+
tokio-rustls-acme = "0.6.0"
|
22
23
|
rcgen = { version = "0.13.2", features = ["x509-parser", "pem"] }
|
23
24
|
base64 = "0.22.1"
|
24
25
|
http-body-util = "0.1.2"
|
25
26
|
hyper = { version = "1.5.0", features = ["full", "server", "http1", "http2"] }
|
26
|
-
tokio = { version = "1", features = ["full"] }
|
27
|
+
tokio = { version = "1.44.1", features = ["full"] }
|
27
28
|
hyper-util = { version = "0.1.10", features = ["full"] }
|
28
29
|
derive_more = { version = "2.0.1", features = ["debug"] }
|
29
30
|
http = "1.3.1"
|
@@ -39,8 +40,11 @@ httparse = "1.10.1"
|
|
39
40
|
async-channel = "2.3.1"
|
40
41
|
tempfile = "3.18.0"
|
41
42
|
sysinfo = "0.33.1"
|
42
|
-
tokio-rustls-acme = "0.6.0"
|
43
43
|
rustls = "0.23.23"
|
44
44
|
fs2 = "0.4.3"
|
45
45
|
ring = "0.17.14"
|
46
46
|
async-trait = "0.1.87"
|
47
|
+
dirs = "6.0.0"
|
48
|
+
regex = "1.11.1"
|
49
|
+
route-recognizer = "0.3.1"
|
50
|
+
fnv = "1.0.7"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
use std::{
|
2
|
+
env::{var, VarError},
|
3
|
+
path::PathBuf,
|
4
|
+
sync::LazyLock,
|
5
|
+
};
|
6
|
+
|
7
|
+
type StringVar = LazyLock<String>;
|
8
|
+
type MaybeStringVar = LazyLock<Result<String, VarError>>;
|
9
|
+
type PathVar = LazyLock<PathBuf>;
|
10
|
+
|
11
|
+
/// ACME Configuration for auto-generating production certificates
|
12
|
+
/// *ITSI_ACME_CACHE_DIR* - Directory to store cached certificates
|
13
|
+
/// so that these are not regenerated every time the server starts
|
14
|
+
pub static ITSI_ACME_CACHE_DIR: StringVar = LazyLock::new(|| {
|
15
|
+
var("ITSI_ACME_CACHE_DIR").unwrap_or_else(|_| "./.rustls_acme_cache".to_string())
|
16
|
+
});
|
17
|
+
|
18
|
+
/// *ITSI_ACME_CONTACT_EMAIL* - Contact Email address to provide to ACME server during certificate renewal
|
19
|
+
pub static ITSI_ACME_CONTACT_EMAIL: MaybeStringVar =
|
20
|
+
LazyLock::new(|| var("ITSI_ACME_CONTACT_EMAIL"));
|
21
|
+
|
22
|
+
/// *ITSI_ACME_CA_PEM_PATH* - Optional CA Pem path, used for testing with non-trusted CAs for certifcate generation.
|
23
|
+
pub static ITSI_ACME_CA_PEM_PATH: MaybeStringVar = LazyLock::new(|| var("ITSI_ACME_CA_PEM_PATH"));
|
24
|
+
|
25
|
+
/// *ITSI_ACME_DIRECTORY_URL* - Directory URL to use for ACME certificate generation.
|
26
|
+
pub static ITSI_ACME_DIRECTORY_URL: StringVar = LazyLock::new(|| {
|
27
|
+
var("ITSI_ACME_DIRECTORY_URL")
|
28
|
+
.unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string())
|
29
|
+
});
|
30
|
+
|
31
|
+
/// *ITSI_ACME_LOCK_FILE_NAME* - Name of the lock file used to prevent concurrent certificate generation.
|
32
|
+
pub static ITSI_ACME_LOCK_FILE_NAME: StringVar =
|
33
|
+
LazyLock::new(|| var("ITSI_ACME_LOCK_FILE_NAME").unwrap_or(".acme.lock".to_string()));
|
34
|
+
|
35
|
+
pub static ITSI_LOCAL_CA_DIR: PathVar = LazyLock::new(|| {
|
36
|
+
var("ITSI_LOCAL_CA_DIR")
|
37
|
+
.map(PathBuf::from)
|
38
|
+
.unwrap_or_else(|_| {
|
39
|
+
dirs::home_dir()
|
40
|
+
.expect("Failed to find HOME directory when initializing ITSI_LOCAL_CA_DIR")
|
41
|
+
.join(".itsi")
|
42
|
+
})
|
43
|
+
});
|
data/ext/itsi_server/src/lib.rs
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
use body_proxy::itsi_body_proxy::ItsiBodyProxy;
|
2
|
-
use magnus::{
|
2
|
+
use magnus::{
|
3
|
+
error::Result, function, method, value::Lazy, Module, Object, RClass, RHash, RModule, Ruby,
|
4
|
+
};
|
5
|
+
use regex::{Regex, RegexSet};
|
3
6
|
use request::itsi_request::ItsiRequest;
|
4
7
|
use response::itsi_response::ItsiResponse;
|
5
8
|
use server::{itsi_server::Server, signal::reset_signal_handlers};
|
6
9
|
use tracing::*;
|
7
10
|
|
8
11
|
pub mod body_proxy;
|
12
|
+
pub mod env;
|
9
13
|
pub mod request;
|
10
14
|
pub mod response;
|
11
15
|
pub mod server;
|
@@ -53,6 +57,70 @@ pub fn log_error(msg: String) {
|
|
53
57
|
error!(msg);
|
54
58
|
}
|
55
59
|
|
60
|
+
const ROUTES: [&str; 39] = [
|
61
|
+
r"(?-u)^/organisations/(?<organisation_id>\d+)/users/(?<user_id>\d+)$",
|
62
|
+
r"(?-u)^/projects/(?<project_id>\d+)/tasks/(?<task_id>\d+)$",
|
63
|
+
r"(?-u)^/products/(?<product_id>\d+)(?:/reviews/(?<review_id>\d+))?$",
|
64
|
+
r"(?-u)^/orders/(?<order_id>\d+)/items(?:/(?<item_id>\d+))?$",
|
65
|
+
r"(?-u)^/posts/(?<post_id>\d+)/comments(?:/(?<comment_id>\d+))?$",
|
66
|
+
r"(?-u)^/teams/(?<team_id>\d+)(?:/members/(?<member_id>\d+))?$",
|
67
|
+
r"(?-u)^/categories/(?<category_id>\d+)/subcategories(?:/(?<subcategory_id>\d+))?$",
|
68
|
+
r"(?-u)^/departments/(?<department_id>\d+)/employees/(?<employee_id>\d+)$",
|
69
|
+
r"(?-u)^/events/(?<event_id>\d+)(?:/sessions/(?<session_id>\d+))?$",
|
70
|
+
r"(?-u)^/invoices/(?<invoice_id>\d+)/payments(?:/(?<payment_id>\d+))?$",
|
71
|
+
r"(?-u)^/tickets/(?<ticket_id>\d+)(?:/responses/(?<response_id>\d+))?$",
|
72
|
+
r"(?-u)^/forums/(?<forum_id>\d+)(?:/threads/(?<thread_id>\d+))?$",
|
73
|
+
r"(?-u)^/subscriptions/(?<subscription_id>\d+)/plans(?:/(?<plan_id>\d+))?$",
|
74
|
+
r"(?-u)^/profiles/(?<profile_id>\d+)/settings$",
|
75
|
+
r"(?-u)^/organizations/(?<organization_id>\d+)/billing(?:/(?<billing_id>\d+))?$",
|
76
|
+
r"(?-u)^/vendors/(?<vendor_id>\d+)/products(?:/(?<product_id>\d+))?$",
|
77
|
+
r"(?-u)^/courses/(?<course_id>\d+)/modules(?:/(?<module_id>\d+))?$",
|
78
|
+
r"(?-u)^/accounts/(?<account_id>\d+)(?:/transactions/(?<transaction_id>\d+))?$",
|
79
|
+
r"(?-u)^/warehouses/(?<warehouse_id>\d+)/inventory(?:/(?<inventory_id>\d+))?$",
|
80
|
+
r"(?-u)^/campaigns/(?<campaign_id>\d+)/ads(?:/(?<ad_id>\d+))?$",
|
81
|
+
r"(?-u)^/applications/(?<application_id>\d+)/stages(?:/(?<stage_id>\d+))?$",
|
82
|
+
r"(?-u)^/notifications/(?<notification_id>\d+)$",
|
83
|
+
r"(?-u)^/albums/(?<album_id>\d+)/photos(?:/(?<photo_id>\d+))?$",
|
84
|
+
r"(?-u)^/news/(?<news_id>\d+)/articles(?:/(?<article_id>\d+))?$",
|
85
|
+
r"(?-u)^/libraries/(?<library_id>\d+)/books(?:/(?<book_id>\d+))?$",
|
86
|
+
r"(?-u)^/universities/(?<university_id>\d+)/students(?:/(?<student_id>\d+))?$",
|
87
|
+
r"(?-u)^/banks/(?<bank_id>\d+)/branches(?:/(?<branch_id>\d+))?$",
|
88
|
+
r"(?-u)^/vehicles/(?<vehicle_id>\d+)/services(?:/(?<service_id>\d+))?$",
|
89
|
+
r"(?-u)^/hotels/(?<hotel_id>\d+)/rooms(?:/(?<room_id>\d+))?$",
|
90
|
+
r"(?-u)^/doctors/(?<doctor_id>\d+)/appointments(?:/(?<appointment_id>\d+))?$",
|
91
|
+
r"(?-u)^/gyms/(?<gym_id>\d+)/memberships(?:/(?<membership_id>\d+))?$",
|
92
|
+
r"(?-u)^/restaurants/(?<restaurant_id>\d+)/menus(?:/(?<menu_id>\d+))?$",
|
93
|
+
r"(?-u)^/parks/(?<park_id>\d+)/events(?:/(?<event_id>\d+))?$",
|
94
|
+
r"(?-u)^/theaters/(?<theater_id>\d+)/shows(?:/(?<show_id>\d+))?$",
|
95
|
+
r"(?-u)^/museums/(?<museum_id>\d+)/exhibits(?:/(?<exhibit_id>\d+))?$",
|
96
|
+
r"(?-u)^/stadiums/(?<stadium_id>\d+)/games(?:/(?<game_id>\d+))?$",
|
97
|
+
r"(?-u)^/schools/(?<school_id>\d+)/classes(?:/(?<class_id>\d+))?$",
|
98
|
+
r"(?-u)^/clubs/(?<club_id>\d+)/events(?:/(?<event_id>\d+))?$",
|
99
|
+
r"(?-u)^/festivals/(?<festival_id>\d+)/tickets(?:/(?<ticket_id>\d+))?$",
|
100
|
+
];
|
101
|
+
use std::sync::LazyLock;
|
102
|
+
|
103
|
+
static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| RegexSet::new(ROUTES).unwrap());
|
104
|
+
static REGEXES: LazyLock<Vec<Regex>> =
|
105
|
+
LazyLock::new(|| ROUTES.iter().map(|&r| Regex::new(r).unwrap()).collect());
|
106
|
+
|
107
|
+
fn match_route(input: String) -> Result<Option<(usize, Option<RHash>)>> {
|
108
|
+
if let Some(index) = REGEX_SET.matches(&input).iter().next() {
|
109
|
+
let regex = ®EXES[index];
|
110
|
+
if let Some(captures) = regex.captures(&input) {
|
111
|
+
let params = RHash::with_capacity(captures.len());
|
112
|
+
for name in regex.capture_names().flatten() {
|
113
|
+
if let Some(value) = captures.name(name) {
|
114
|
+
params.aset(name, value.as_str()).ok();
|
115
|
+
}
|
116
|
+
}
|
117
|
+
return Ok(Some((index, Some(params))));
|
118
|
+
}
|
119
|
+
return Ok(Some((index, None)));
|
120
|
+
}
|
121
|
+
Ok(None)
|
122
|
+
}
|
123
|
+
|
56
124
|
#[magnus::init]
|
57
125
|
fn init(ruby: &Ruby) -> Result<()> {
|
58
126
|
itsi_tracing::init();
|
@@ -61,6 +129,7 @@ fn init(ruby: &Ruby) -> Result<()> {
|
|
61
129
|
.ok();
|
62
130
|
|
63
131
|
let itsi = ruby.get_inner(&ITSI_MODULE);
|
132
|
+
itsi.define_singleton_method("match_route", function!(match_route, 1))?;
|
64
133
|
itsi.define_singleton_method("log_debug", function!(log_debug, 1))?;
|
65
134
|
itsi.define_singleton_method("log_info", function!(log_info, 1))?;
|
66
135
|
itsi.define_singleton_method("log_warn", function!(log_warn, 1))?;
|
@@ -70,6 +139,7 @@ fn init(ruby: &Ruby) -> Result<()> {
|
|
70
139
|
server.define_singleton_method("new", function!(Server::new, -1))?;
|
71
140
|
server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
|
72
141
|
server.define_method("start", method!(Server::start, 0))?;
|
142
|
+
server.define_method("stop", method!(Server::stop, 0))?;
|
73
143
|
|
74
144
|
let request = ruby.get_inner(&ITSI_REQUEST);
|
75
145
|
request.define_method("path", method!(ItsiRequest::path, 0))?;
|
@@ -85,6 +155,8 @@ fn init(ruby: &Ruby) -> Result<()> {
|
|
85
155
|
request.define_method("port", method!(ItsiRequest::port, 0))?;
|
86
156
|
request.define_method("body", method!(ItsiRequest::body, 0))?;
|
87
157
|
request.define_method("response", method!(ItsiRequest::response, 0))?;
|
158
|
+
request.define_method("json?", method!(ItsiRequest::is_json, 0))?;
|
159
|
+
request.define_method("html?", method!(ItsiRequest::is_html, 0))?;
|
88
160
|
|
89
161
|
let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY);
|
90
162
|
body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?;
|
@@ -101,6 +173,8 @@ fn init(ruby: &Ruby) -> Result<()> {
|
|
101
173
|
response.define_method("close_read", method!(ItsiResponse::close_read, 0))?;
|
102
174
|
response.define_method("close", method!(ItsiResponse::close, 0))?;
|
103
175
|
response.define_method("hijack", method!(ItsiResponse::hijack, 1))?;
|
176
|
+
response.define_method("json?", method!(ItsiResponse::is_json, 0))?;
|
177
|
+
response.define_method("html?", method!(ItsiResponse::is_html, 0))?;
|
104
178
|
|
105
179
|
Ok(())
|
106
180
|
}
|
@@ -6,17 +6,18 @@ use crate::{
|
|
6
6
|
response::itsi_response::ItsiResponse,
|
7
7
|
server::{
|
8
8
|
itsi_server::{RequestJob, Server},
|
9
|
-
listener::{
|
9
|
+
listener::{ListenerInfo, SockAddr},
|
10
10
|
serve_strategy::single_mode::RunningPhase,
|
11
11
|
},
|
12
12
|
};
|
13
13
|
use bytes::Bytes;
|
14
14
|
use derive_more::Debug;
|
15
15
|
use futures::StreamExt;
|
16
|
-
use http::{request::Parts, Response, StatusCode};
|
16
|
+
use http::{request::Parts, HeaderValue, Response, StatusCode};
|
17
17
|
use http_body_util::{combinators::BoxBody, BodyExt, Empty};
|
18
18
|
use hyper::{body::Incoming, Request};
|
19
19
|
use itsi_error::from::CLIENT_CONNECTION_CLOSED;
|
20
|
+
use itsi_rb_helpers::print_rb_backtrace;
|
20
21
|
use itsi_tracing::{debug, error};
|
21
22
|
use magnus::{
|
22
23
|
error::{ErrorType, Result as MagnusResult},
|
@@ -33,7 +34,6 @@ use tokio::sync::{
|
|
33
34
|
};
|
34
35
|
static ID_CALL: LazyId = LazyId::new("call");
|
35
36
|
static ID_MESSAGE: LazyId = LazyId::new("message");
|
36
|
-
static ID_BACKTRACE: LazyId = LazyId::new("backtrace");
|
37
37
|
|
38
38
|
#[derive(Debug)]
|
39
39
|
#[magnus::wrap(class = "Itsi::Request", free_immediately, size)]
|
@@ -44,11 +44,12 @@ pub struct ItsiRequest {
|
|
44
44
|
pub remote_addr: String,
|
45
45
|
pub version: String,
|
46
46
|
#[debug(skip)]
|
47
|
-
pub(crate) listener: Arc<
|
47
|
+
pub(crate) listener: Arc<ListenerInfo>,
|
48
48
|
#[debug(skip)]
|
49
49
|
pub server: Arc<Server>,
|
50
50
|
pub response: ItsiResponse,
|
51
51
|
pub start: Instant,
|
52
|
+
pub content_type: String,
|
52
53
|
}
|
53
54
|
|
54
55
|
impl fmt::Display for ItsiRequest {
|
@@ -82,6 +83,14 @@ impl ItsiRequest {
|
|
82
83
|
}
|
83
84
|
}
|
84
85
|
|
86
|
+
pub fn is_json(&self) -> bool {
|
87
|
+
self.content_type.eq("application/json")
|
88
|
+
}
|
89
|
+
|
90
|
+
pub fn is_html(&self) -> bool {
|
91
|
+
self.content_type.eq("text/html")
|
92
|
+
}
|
93
|
+
|
85
94
|
pub fn process(
|
86
95
|
self,
|
87
96
|
ruby: &Ruby,
|
@@ -106,14 +115,7 @@ impl ItsiRequest {
|
|
106
115
|
debug!("Connection closed by client");
|
107
116
|
response.close();
|
108
117
|
} else if let Some(rb_err) = err.value() {
|
109
|
-
|
110
|
-
.funcall::<_, _, Vec<String>>(*ID_BACKTRACE, ())
|
111
|
-
.unwrap_or_default();
|
112
|
-
|
113
|
-
error!("Error occurred in Handler: {:?}", rb_err);
|
114
|
-
for line in backtrace {
|
115
|
-
error!("{}", line);
|
116
|
-
}
|
118
|
+
print_rb_backtrace(rb_err);
|
117
119
|
response.internal_server_error(err.to_string());
|
118
120
|
} else {
|
119
121
|
response.internal_server_error(err.to_string());
|
@@ -128,7 +130,7 @@ impl ItsiRequest {
|
|
128
130
|
hyper_request: Request<Incoming>,
|
129
131
|
sender: async_channel::Sender<RequestJob>,
|
130
132
|
server: Arc<Server>,
|
131
|
-
listener: Arc<
|
133
|
+
listener: Arc<ListenerInfo>,
|
132
134
|
addr: SockAddr,
|
133
135
|
shutdown_rx: watch::Receiver<RunningPhase>,
|
134
136
|
) -> itsi_error::Result<Response<BoxBody<Bytes, Infallible>>> {
|
@@ -153,7 +155,7 @@ impl ItsiRequest {
|
|
153
155
|
request: Request<Incoming>,
|
154
156
|
sock_addr: SockAddr,
|
155
157
|
server: Arc<Server>,
|
156
|
-
listener: Arc<
|
158
|
+
listener: Arc<ListenerInfo>,
|
157
159
|
) -> (ItsiRequest, mpsc::Receiver<Option<Bytes>>) {
|
158
160
|
let (parts, body) = request.into_parts();
|
159
161
|
let body = if server.stream_body.is_some_and(|f| f) {
|
@@ -175,8 +177,27 @@ impl ItsiRequest {
|
|
175
177
|
server,
|
176
178
|
listener,
|
177
179
|
version: format!("{:?}", &parts.version),
|
178
|
-
response: ItsiResponse::new(
|
180
|
+
response: ItsiResponse::new(
|
181
|
+
parts.clone(),
|
182
|
+
response_channel.0,
|
183
|
+
parts
|
184
|
+
.headers
|
185
|
+
.get("Accept")
|
186
|
+
.unwrap_or(&HeaderValue::from_static("text/html"))
|
187
|
+
.to_str()
|
188
|
+
.unwrap()
|
189
|
+
.to_string(),
|
190
|
+
),
|
179
191
|
start: Instant::now(),
|
192
|
+
content_type: parts
|
193
|
+
.headers
|
194
|
+
.get("Content-Type")
|
195
|
+
.unwrap_or(&HeaderValue::from_static(
|
196
|
+
"application/x-www-form-urlencoded",
|
197
|
+
))
|
198
|
+
.to_str()
|
199
|
+
.unwrap()
|
200
|
+
.to_string(),
|
180
201
|
parts,
|
181
202
|
},
|
182
203
|
response_channel.1,
|
@@ -231,7 +252,7 @@ impl ItsiRequest {
|
|
231
252
|
.uri
|
232
253
|
.host()
|
233
254
|
.map(|host| host.to_string())
|
234
|
-
.unwrap_or_else(|| self.listener.host()))
|
255
|
+
.unwrap_or_else(|| self.listener.host.clone()))
|
235
256
|
}
|
236
257
|
|
237
258
|
pub(crate) fn scheme(&self) -> MagnusResult<String> {
|
@@ -240,7 +261,7 @@ impl ItsiRequest {
|
|
240
261
|
.uri
|
241
262
|
.scheme()
|
242
263
|
.map(|scheme| scheme.to_string())
|
243
|
-
.unwrap_or_else(|| self.listener.scheme()))
|
264
|
+
.unwrap_or_else(|| self.listener.scheme.clone()))
|
244
265
|
}
|
245
266
|
|
246
267
|
pub(crate) fn headers(&self) -> MagnusResult<Vec<(String, &str)>> {
|
@@ -264,7 +285,7 @@ impl ItsiRequest {
|
|
264
285
|
}
|
265
286
|
|
266
287
|
pub(crate) fn port(&self) -> MagnusResult<u16> {
|
267
|
-
Ok(self.parts.uri.port_u16().unwrap_or(self.listener.port
|
288
|
+
Ok(self.parts.uri.port_u16().unwrap_or(self.listener.port))
|
268
289
|
}
|
269
290
|
|
270
291
|
pub(crate) fn body(&self) -> MagnusResult<Value> {
|
@@ -37,6 +37,7 @@ use crate::server::serve_strategy::single_mode::RunningPhase;
|
|
37
37
|
#[derive(Debug, Clone)]
|
38
38
|
pub struct ItsiResponse {
|
39
39
|
pub data: Arc<ResponseData>,
|
40
|
+
pub accept: String,
|
40
41
|
}
|
41
42
|
|
42
43
|
#[derive(Debug)]
|
@@ -76,7 +77,6 @@ impl ItsiResponse {
|
|
76
77
|
(ReceiverStream::new(receiver), shutdown_rx),
|
77
78
|
|(mut receiver, mut shutdown_rx)| async move {
|
78
79
|
if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
|
79
|
-
warn!("Disconnecting streaming client.");
|
80
80
|
return None;
|
81
81
|
}
|
82
82
|
loop {
|
@@ -279,7 +279,8 @@ impl ItsiResponse {
|
|
279
279
|
if let Some(writer) = writer.write().as_ref() {
|
280
280
|
writer
|
281
281
|
.blocking_send(Some(frame))
|
282
|
-
.map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)
|
282
|
+
.map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)
|
283
|
+
.ok();
|
283
284
|
}
|
284
285
|
Ok(0)
|
285
286
|
}
|
@@ -293,11 +294,19 @@ impl ItsiResponse {
|
|
293
294
|
Ok(true)
|
294
295
|
}
|
295
296
|
|
297
|
+
pub fn is_html(&self) -> bool {
|
298
|
+
self.accept.starts_with("text/html")
|
299
|
+
}
|
300
|
+
|
301
|
+
pub fn is_json(&self) -> bool {
|
302
|
+
self.accept.starts_with("application/json")
|
303
|
+
}
|
304
|
+
|
296
305
|
pub fn close_read(&self) -> MagnusResult<bool> {
|
297
|
-
|
306
|
+
Ok(true)
|
298
307
|
}
|
299
308
|
|
300
|
-
pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes
|
309
|
+
pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes>>, accept: String) -> Self {
|
301
310
|
Self {
|
302
311
|
data: Arc::new(ResponseData {
|
303
312
|
response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
|
@@ -306,6 +315,7 @@ impl ItsiResponse {
|
|
306
315
|
hijacked_socket: RwLock::new(None),
|
307
316
|
parts,
|
308
317
|
}),
|
318
|
+
accept,
|
309
319
|
}
|
310
320
|
}
|
311
321
|
|
@@ -9,6 +9,7 @@ use std::{
|
|
9
9
|
path::PathBuf,
|
10
10
|
str::FromStr,
|
11
11
|
};
|
12
|
+
|
12
13
|
#[derive(Debug, Clone)]
|
13
14
|
pub enum BindAddress {
|
14
15
|
Ip(IpAddr),
|
@@ -100,7 +101,7 @@ impl FromStr for Bind {
|
|
100
101
|
"IPv6 addresses must use [ ] when specifying a port".to_owned(),
|
101
102
|
));
|
102
103
|
} else {
|
103
|
-
(h,
|
104
|
+
(h, p.parse::<u16>().ok()) // Treat as a hostname
|
104
105
|
}
|
105
106
|
} else {
|
106
107
|
(url, None)
|
@@ -109,18 +110,22 @@ impl FromStr for Bind {
|
|
109
110
|
let address = if let Ok(ip) = host.parse::<IpAddr>() {
|
110
111
|
BindAddress::Ip(ip)
|
111
112
|
} else {
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
113
|
+
match protocol {
|
114
|
+
BindProtocol::Https | BindProtocol::Http => resolve_hostname(host)
|
115
|
+
.map(BindAddress::Ip)
|
116
|
+
.ok_or(ItsiError::ArgumentError(format!(
|
117
|
+
"Failed to resolve hostname {}",
|
118
|
+
host
|
119
|
+
)))?,
|
120
|
+
BindProtocol::Unix | BindProtocol::Unixs => BindAddress::UnixSocket(host.into()),
|
121
|
+
}
|
118
122
|
};
|
119
|
-
|
120
|
-
|
121
|
-
BindProtocol::
|
122
|
-
BindProtocol::
|
123
|
-
BindProtocol::
|
123
|
+
|
124
|
+
let port = match protocol {
|
125
|
+
BindProtocol::Http => port.or(Some(80)),
|
126
|
+
BindProtocol::Https => port.or(Some(443)),
|
127
|
+
BindProtocol::Unix => None,
|
128
|
+
BindProtocol::Unixs => None,
|
124
129
|
};
|
125
130
|
|
126
131
|
let tls_config = match protocol {
|
@@ -129,13 +134,13 @@ impl FromStr for Bind {
|
|
129
134
|
BindProtocol::Unix => None,
|
130
135
|
BindProtocol::Unixs => Some(configure_tls(host, &options)?),
|
131
136
|
};
|
132
|
-
|
133
|
-
Ok(Self {
|
137
|
+
let bind = Self {
|
134
138
|
address,
|
135
139
|
port,
|
136
140
|
protocol,
|
137
141
|
tls_config,
|
138
|
-
}
|
142
|
+
};
|
143
|
+
Ok(bind)
|
139
144
|
}
|
140
145
|
}
|
141
146
|
|