itsi-scheduler 0.1.5 → 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.
Potentially problematic release.
This version of itsi-scheduler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +90 -22
- data/README.md +5 -0
- data/_index.md +7 -0
- data/ext/itsi_error/Cargo.toml +1 -0
- data/ext/itsi_error/src/lib.rs +106 -7
- 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_rb_helpers/Cargo.toml +1 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +18 -0
- data/ext/itsi_rb_helpers/src/lib.rs +59 -9
- 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/src/itsi_scheduler.rs +1 -1
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +72 -28
- 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 +113 -75
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
- data/ext/itsi_server/src/{body_proxy/itsi_body_proxy.rs → ruby_types/itsi_body_proxy/mod.rs} +29 -8
- 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/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +84 -40
- 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/{bind.rs → binds/bind.rs} +56 -24
- data/ext/itsi_server/src/server/{listener.rs → binds/listener.rs} +218 -113
- data/ext/itsi_server/src/server/binds/mod.rs +4 -0
- data/ext/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +55 -17
- data/ext/itsi_server/src/server/{tls.rs → binds/tls.rs} +109 -28
- 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 +2 -1
- data/ext/itsi_server/src/server/lifecycle_event.rs +3 -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 +6 -5
- data/ext/itsi_server/src/server/process_worker.rs +65 -14
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +137 -49
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +9 -6
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +338 -164
- data/ext/itsi_server/src/server/signal.rs +32 -26
- data/ext/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/ext/itsi_server/src/server/thread_worker.rs +214 -107
- 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 +1 -0
- data/ext/itsi_tracing/src/lib.rs +312 -34
- 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/scheduler/version.rb +1 -1
- data/lib/itsi/scheduler.rb +2 -2
- metadata +93 -21
- data/ext/itsi_error/src/from.rs +0 -71
- data/ext/itsi_server/extconf.rb +0 -6
- data/ext/itsi_server/src/body_proxy/mod.rs +0 -2
- data/ext/itsi_server/src/request/itsi_request.rs +0 -277
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/response/mod.rs +0 -1
- 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/src/server/itsi_server.rs +0 -244
- /data/ext/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
@@ -0,0 +1,345 @@
|
|
1
|
+
use derive_more::Debug;
|
2
|
+
use futures::StreamExt;
|
3
|
+
use http::{header::CONTENT_LENGTH, request::Parts, Response, StatusCode, Version};
|
4
|
+
use http_body_util::{combinators::BoxBody, BodyExt, Empty};
|
5
|
+
use itsi_error::CLIENT_CONNECTION_CLOSED;
|
6
|
+
use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
|
7
|
+
use itsi_tracing::{debug, error};
|
8
|
+
use magnus::{
|
9
|
+
block::Proc,
|
10
|
+
error::{ErrorType, Result as MagnusResult},
|
11
|
+
Error, RHash, Symbol,
|
12
|
+
};
|
13
|
+
use magnus::{
|
14
|
+
value::{LazyId, ReprValue},
|
15
|
+
Ruby, Value,
|
16
|
+
};
|
17
|
+
use std::{fmt, io::Write, sync::Arc, time::Instant};
|
18
|
+
use tokio::sync::mpsc::{self};
|
19
|
+
|
20
|
+
use super::{
|
21
|
+
itsi_body_proxy::{big_bytes::BigBytes, ItsiBody, ItsiBodyProxy},
|
22
|
+
itsi_http_response::ItsiHttpResponse,
|
23
|
+
};
|
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,
|
32
|
+
};
|
33
|
+
|
34
|
+
static ID_MESSAGE: LazyId = LazyId::new("message");
|
35
|
+
|
36
|
+
#[derive(Debug)]
|
37
|
+
#[magnus::wrap(class = "Itsi::HttpRequest", free_immediately, size)]
|
38
|
+
pub struct ItsiHttpRequest {
|
39
|
+
pub parts: Parts,
|
40
|
+
#[debug(skip)]
|
41
|
+
pub body: ItsiBody,
|
42
|
+
pub version: Version,
|
43
|
+
pub response: ItsiHttpResponse,
|
44
|
+
pub start: Instant,
|
45
|
+
#[debug(skip)]
|
46
|
+
pub context: HttpRequestContext,
|
47
|
+
pub script_name: String,
|
48
|
+
}
|
49
|
+
|
50
|
+
impl fmt::Display for ItsiHttpRequest {
|
51
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
52
|
+
write!(
|
53
|
+
f,
|
54
|
+
"{} {} {}",
|
55
|
+
self.version().unwrap(),
|
56
|
+
self.method().unwrap(),
|
57
|
+
self.path().unwrap()
|
58
|
+
)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
impl ItsiHttpRequest {
|
63
|
+
pub fn is_connection_closed_err(ruby: &Ruby, err: &Error) -> bool {
|
64
|
+
match err.error_type() {
|
65
|
+
ErrorType::Jump(_) => false,
|
66
|
+
ErrorType::Error(_, _) => false,
|
67
|
+
ErrorType::Exception(exception) => {
|
68
|
+
exception.is_kind_of(ruby.exception_eof_error())
|
69
|
+
&& err
|
70
|
+
.value()
|
71
|
+
.map(|v| {
|
72
|
+
v.funcall::<_, _, String>(*ID_MESSAGE, ())
|
73
|
+
.unwrap_or("".to_string())
|
74
|
+
.eq(CLIENT_CONNECTION_CLOSED)
|
75
|
+
})
|
76
|
+
.unwrap_or(false)
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
pub fn content_type_str(&self) -> &str {
|
81
|
+
self.parts
|
82
|
+
.headers
|
83
|
+
.get("Content-Type")
|
84
|
+
.and_then(|hv| hv.to_str().ok())
|
85
|
+
.unwrap_or("application/x-www-form-urlencoded")
|
86
|
+
}
|
87
|
+
|
88
|
+
pub fn is_json(&self) -> bool {
|
89
|
+
self.content_type_str() == "application/json"
|
90
|
+
}
|
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
|
+
|
115
|
+
pub fn is_html(&self) -> bool {
|
116
|
+
self.content_type_str() == "text/html"
|
117
|
+
}
|
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
|
+
|
135
|
+
pub fn process(self, ruby: &Ruby, app_proc: Arc<HeapValue<Proc>>) -> magnus::error::Result<()> {
|
136
|
+
let response = self.response.clone();
|
137
|
+
let result = app_proc.call::<_, Value>((self,));
|
138
|
+
if let Err(err) = result {
|
139
|
+
Self::internal_error(ruby, response, err);
|
140
|
+
}
|
141
|
+
Ok(())
|
142
|
+
}
|
143
|
+
|
144
|
+
pub fn internal_error(ruby: &Ruby, response: ItsiHttpResponse, err: Error) {
|
145
|
+
if Self::is_connection_closed_err(ruby, &err) {
|
146
|
+
debug!("Connection closed by client");
|
147
|
+
response.close();
|
148
|
+
} else if let Some(rb_err) = err.value() {
|
149
|
+
print_rb_backtrace(rb_err);
|
150
|
+
response.internal_server_error(err.to_string());
|
151
|
+
} else {
|
152
|
+
response.internal_server_error(err.to_string());
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
pub fn error(self, message: String) {
|
157
|
+
self.response.internal_server_error(message);
|
158
|
+
}
|
159
|
+
|
160
|
+
pub(crate) async fn process_request(
|
161
|
+
app: Arc<HeapValue<Proc>>,
|
162
|
+
hyper_request: HttpRequest,
|
163
|
+
context: &HttpRequestContext,
|
164
|
+
script_name: String,
|
165
|
+
nonblocking: bool,
|
166
|
+
) -> itsi_error::Result<HttpResponse> {
|
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
|
+
}
|
195
|
+
}
|
196
|
+
Err(err_resp) => Ok(err_resp),
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
pub(crate) async fn new(
|
201
|
+
request: HttpRequest,
|
202
|
+
context: &HttpRequestContext,
|
203
|
+
script_name: String,
|
204
|
+
) -> Result<(ItsiHttpRequest, mpsc::Receiver<ByteFrame>), HttpResponse> {
|
205
|
+
let (parts, body) = request.into_parts();
|
206
|
+
let body = if context.server_params.streamable_body {
|
207
|
+
ItsiBody::Stream(ItsiBodyProxy::new(body))
|
208
|
+
} else {
|
209
|
+
let mut body_bytes = BigBytes::new();
|
210
|
+
let mut stream = body.into_data_stream();
|
211
|
+
while let Some(chunk) = stream.next().await {
|
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
|
+
}
|
222
|
+
}
|
223
|
+
ItsiBody::Buffered(body_bytes)
|
224
|
+
};
|
225
|
+
let response_channel = mpsc::channel::<ByteFrame>(100);
|
226
|
+
Ok((
|
227
|
+
Self {
|
228
|
+
context: context.clone(),
|
229
|
+
version: parts.version,
|
230
|
+
response: ItsiHttpResponse::new(parts.clone(), response_channel.0),
|
231
|
+
start: Instant::now(),
|
232
|
+
script_name,
|
233
|
+
body,
|
234
|
+
parts,
|
235
|
+
},
|
236
|
+
response_channel.1,
|
237
|
+
))
|
238
|
+
}
|
239
|
+
|
240
|
+
pub(crate) fn path(&self) -> MagnusResult<&str> {
|
241
|
+
Ok(self
|
242
|
+
.parts
|
243
|
+
.uri
|
244
|
+
.path()
|
245
|
+
.strip_prefix(&self.script_name)
|
246
|
+
.unwrap_or(self.parts.uri.path()))
|
247
|
+
}
|
248
|
+
|
249
|
+
pub(crate) fn script_name(&self) -> MagnusResult<&str> {
|
250
|
+
Ok(&self.script_name)
|
251
|
+
}
|
252
|
+
|
253
|
+
pub(crate) fn query_string(&self) -> MagnusResult<&str> {
|
254
|
+
Ok(self.parts.uri.query().unwrap_or(""))
|
255
|
+
}
|
256
|
+
|
257
|
+
pub(crate) fn method(&self) -> MagnusResult<&str> {
|
258
|
+
Ok(self.parts.method.as_str())
|
259
|
+
}
|
260
|
+
|
261
|
+
pub(crate) fn version(&self) -> MagnusResult<&str> {
|
262
|
+
Ok(match self.version {
|
263
|
+
Version::HTTP_09 => "HTTP/0.9",
|
264
|
+
Version::HTTP_10 => "HTTP/1.0",
|
265
|
+
Version::HTTP_11 => "HTTP/1.1",
|
266
|
+
Version::HTTP_2 => "HTTP/2.0",
|
267
|
+
Version::HTTP_3 => "HTTP/3.0",
|
268
|
+
_ => "HTTP/Unknown",
|
269
|
+
})
|
270
|
+
}
|
271
|
+
|
272
|
+
pub(crate) fn rack_protocol(&self) -> MagnusResult<Vec<&str>> {
|
273
|
+
Ok(self
|
274
|
+
.parts
|
275
|
+
.headers
|
276
|
+
.get("upgrade")
|
277
|
+
.or_else(|| self.parts.headers.get("protocol"))
|
278
|
+
.map(|value| {
|
279
|
+
value
|
280
|
+
.to_str()
|
281
|
+
.unwrap_or("")
|
282
|
+
.split(',')
|
283
|
+
.map(|s| s.trim())
|
284
|
+
.collect::<Vec<&str>>()
|
285
|
+
})
|
286
|
+
.unwrap_or_else(|| vec!["http"]))
|
287
|
+
}
|
288
|
+
|
289
|
+
pub(crate) fn host(&self) -> MagnusResult<&str> {
|
290
|
+
Ok(self
|
291
|
+
.parts
|
292
|
+
.uri
|
293
|
+
.host()
|
294
|
+
.unwrap_or_else(|| &self.context.listener.host))
|
295
|
+
}
|
296
|
+
|
297
|
+
pub(crate) fn scheme(&self) -> MagnusResult<&str> {
|
298
|
+
Ok(self
|
299
|
+
.parts
|
300
|
+
.uri
|
301
|
+
.scheme()
|
302
|
+
.map(|scheme| scheme.as_str())
|
303
|
+
.unwrap_or_else(|| &self.context.listener.scheme))
|
304
|
+
}
|
305
|
+
|
306
|
+
pub(crate) fn headers(&self) -> MagnusResult<Vec<(&str, &str)>> {
|
307
|
+
Ok(self
|
308
|
+
.parts
|
309
|
+
.headers
|
310
|
+
.iter()
|
311
|
+
.map(|(hn, hv)| (hn.as_str(), hv.to_str().unwrap_or("")))
|
312
|
+
.collect::<Vec<(&str, &str)>>())
|
313
|
+
}
|
314
|
+
|
315
|
+
pub fn header(&self, name: String) -> MagnusResult<Option<Vec<&str>>> {
|
316
|
+
let result: Vec<&str> = self
|
317
|
+
.parts
|
318
|
+
.headers
|
319
|
+
.get_all(&name)
|
320
|
+
.iter()
|
321
|
+
.filter_map(|value| value.to_str().ok())
|
322
|
+
.collect();
|
323
|
+
Ok(Some(result))
|
324
|
+
}
|
325
|
+
|
326
|
+
pub(crate) fn remote_addr(&self) -> MagnusResult<&str> {
|
327
|
+
Ok(&self.context.addr)
|
328
|
+
}
|
329
|
+
|
330
|
+
pub(crate) fn port(&self) -> MagnusResult<u16> {
|
331
|
+
Ok(self
|
332
|
+
.parts
|
333
|
+
.uri
|
334
|
+
.port_u16()
|
335
|
+
.unwrap_or(self.context.listener.port))
|
336
|
+
}
|
337
|
+
|
338
|
+
pub(crate) fn body(&self) -> MagnusResult<Option<Value>> {
|
339
|
+
Ok(self.body.into_value())
|
340
|
+
}
|
341
|
+
|
342
|
+
pub(crate) fn response(&self) -> MagnusResult<ItsiHttpResponse> {
|
343
|
+
Ok(self.response.clone())
|
344
|
+
}
|
345
|
+
}
|
@@ -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};
|
@@ -13,7 +14,7 @@ use itsi_tracing::error;
|
|
13
14
|
use magnus::error::Result as MagnusResult;
|
14
15
|
use parking_lot::RwLock;
|
15
16
|
use std::{
|
16
|
-
|
17
|
+
collections::HashMap,
|
17
18
|
io,
|
18
19
|
os::{fd::FromRawFd, unix::net::UnixStream},
|
19
20
|
str::FromStr,
|
@@ -31,30 +32,33 @@ use tokio_stream::wrappers::ReceiverStream;
|
|
31
32
|
use tokio_util::io::ReaderStream;
|
32
33
|
use tracing::warn;
|
33
34
|
|
34
|
-
use crate::server::
|
35
|
+
use crate::server::{
|
36
|
+
byte_frame::ByteFrame, http_message_types::HttpResponse,
|
37
|
+
serve_strategy::single_mode::RunningPhase,
|
38
|
+
};
|
35
39
|
|
36
|
-
#[magnus::wrap(class = "Itsi::
|
40
|
+
#[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)]
|
37
41
|
#[derive(Debug, Clone)]
|
38
|
-
pub struct
|
42
|
+
pub struct ItsiHttpResponse {
|
39
43
|
pub data: Arc<ResponseData>,
|
40
44
|
}
|
41
45
|
|
42
46
|
#[derive(Debug)]
|
43
47
|
pub struct ResponseData {
|
44
|
-
pub response: RwLock<Option<
|
45
|
-
pub response_writer: RwLock<Option<mpsc::Sender<
|
48
|
+
pub response: RwLock<Option<HttpResponse>>,
|
49
|
+
pub response_writer: RwLock<Option<mpsc::Sender<ByteFrame>>>,
|
46
50
|
pub response_buffer: RwLock<BytesMut>,
|
47
51
|
pub hijacked_socket: RwLock<Option<UnixStream>>,
|
48
52
|
pub parts: Parts,
|
49
53
|
}
|
50
54
|
|
51
|
-
impl
|
55
|
+
impl ItsiHttpResponse {
|
52
56
|
pub async fn build(
|
53
57
|
&self,
|
54
|
-
first_frame:
|
55
|
-
receiver: mpsc::Receiver<
|
58
|
+
first_frame: ByteFrame,
|
59
|
+
receiver: mpsc::Receiver<ByteFrame>,
|
56
60
|
shutdown_rx: watch::Receiver<RunningPhase>,
|
57
|
-
) ->
|
61
|
+
) -> HttpResponse {
|
58
62
|
if self.is_hijacked() {
|
59
63
|
return match self.process_hijacked_response().await {
|
60
64
|
Ok(result) => result,
|
@@ -64,31 +68,30 @@ impl ItsiResponse {
|
|
64
68
|
}
|
65
69
|
};
|
66
70
|
}
|
67
|
-
|
68
71
|
let mut response = self.data.response.write().take().unwrap();
|
69
|
-
*response.body_mut() = if first_frame
|
72
|
+
*response.body_mut() = if matches!(first_frame, ByteFrame::Empty) {
|
70
73
|
BoxBody::new(Empty::new())
|
71
|
-
} else if
|
72
|
-
BoxBody::new(Full::new(first_frame.
|
74
|
+
} else if matches!(first_frame, ByteFrame::End(_)) {
|
75
|
+
BoxBody::new(Full::new(first_frame.into()))
|
73
76
|
} else {
|
74
|
-
let initial_frame = tokio_stream::once(Ok(Frame::data(first_frame
|
77
|
+
let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame))));
|
75
78
|
let frame_stream = unfold(
|
76
79
|
(ReceiverStream::new(receiver), shutdown_rx),
|
77
80
|
|(mut receiver, mut shutdown_rx)| async move {
|
78
81
|
if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
|
79
|
-
warn!("Disconnecting streaming client.");
|
80
82
|
return None;
|
81
83
|
}
|
82
84
|
loop {
|
83
85
|
tokio::select! {
|
84
86
|
maybe_bytes = receiver.next() => {
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
} else {
|
89
|
-
// Receiver closed, end the stream.
|
90
|
-
return None;
|
87
|
+
match maybe_bytes {
|
88
|
+
Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => {
|
89
|
+
return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx)));
|
91
90
|
}
|
91
|
+
_ => {
|
92
|
+
return None;
|
93
|
+
}
|
94
|
+
}
|
92
95
|
},
|
93
96
|
_ = shutdown_rx.changed() => {
|
94
97
|
match *shutdown_rx.borrow() {
|
@@ -184,7 +187,7 @@ impl ItsiResponse {
|
|
184
187
|
Ok((headers, status, requires_upgrade, reader))
|
185
188
|
}
|
186
189
|
|
187
|
-
pub async fn process_hijacked_response(&self) -> Result<
|
190
|
+
pub async fn process_hijacked_response(&self) -> Result<HttpResponse> {
|
188
191
|
let (headers, status, requires_upgrade, reader) = self.read_hijacked_headers().await?;
|
189
192
|
let mut response = if requires_upgrade {
|
190
193
|
let parts = self.data.parts.clone();
|
@@ -261,27 +264,31 @@ impl ItsiResponse {
|
|
261
264
|
}
|
262
265
|
}
|
263
266
|
|
264
|
-
pub fn send_frame(&self, frame: Bytes) -> MagnusResult<
|
265
|
-
self.send_frame_into(frame, &self.data.response_writer)
|
267
|
+
pub fn send_frame(&self, frame: Bytes) -> MagnusResult<()> {
|
268
|
+
self.send_frame_into(ByteFrame::Data(frame), &self.data.response_writer)
|
269
|
+
}
|
270
|
+
|
271
|
+
pub fn recv_frame(&self) {
|
272
|
+
// not implemented
|
266
273
|
}
|
267
274
|
|
268
|
-
pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<
|
269
|
-
let result = self.send_frame_into(frame, &self.data.response_writer);
|
275
|
+
pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<()> {
|
276
|
+
let result = self.send_frame_into(ByteFrame::End(frame), &self.data.response_writer);
|
270
277
|
self.data.response_writer.write().take();
|
271
278
|
result
|
272
279
|
}
|
273
280
|
|
274
281
|
pub fn send_frame_into(
|
275
282
|
&self,
|
276
|
-
frame:
|
277
|
-
writer: &RwLock<Option<mpsc::Sender<
|
278
|
-
) -> MagnusResult<
|
283
|
+
frame: ByteFrame,
|
284
|
+
writer: &RwLock<Option<mpsc::Sender<ByteFrame>>>,
|
285
|
+
) -> MagnusResult<()> {
|
279
286
|
if let Some(writer) = writer.write().as_ref() {
|
280
|
-
writer
|
281
|
-
.blocking_send(
|
282
|
-
.map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)
|
287
|
+
return Ok(writer
|
288
|
+
.blocking_send(frame)
|
289
|
+
.map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?);
|
283
290
|
}
|
284
|
-
Ok(
|
291
|
+
Ok(())
|
285
292
|
}
|
286
293
|
|
287
294
|
pub fn is_hijacked(&self) -> bool {
|
@@ -293,11 +300,28 @@ impl ItsiResponse {
|
|
293
300
|
Ok(true)
|
294
301
|
}
|
295
302
|
|
303
|
+
pub fn accept_str(&self) -> &str {
|
304
|
+
self.data
|
305
|
+
.parts
|
306
|
+
.headers
|
307
|
+
.get(ACCEPT)
|
308
|
+
.and_then(|hv| hv.to_str().ok()) // handle invalid utf-8
|
309
|
+
.unwrap_or("application/x-www-form-urlencoded")
|
310
|
+
}
|
311
|
+
|
312
|
+
pub fn is_html(&self) -> bool {
|
313
|
+
self.accept_str().starts_with("text/html")
|
314
|
+
}
|
315
|
+
|
316
|
+
pub fn is_json(&self) -> bool {
|
317
|
+
self.accept_str().starts_with("application/json")
|
318
|
+
}
|
319
|
+
|
296
320
|
pub fn close_read(&self) -> MagnusResult<bool> {
|
297
|
-
|
321
|
+
Ok(true)
|
298
322
|
}
|
299
323
|
|
300
|
-
pub fn new(parts: Parts, response_writer: mpsc::Sender<
|
324
|
+
pub fn new(parts: Parts, response_writer: mpsc::Sender<ByteFrame>) -> Self {
|
301
325
|
Self {
|
302
326
|
data: Arc::new(ResponseData {
|
303
327
|
response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
|
@@ -320,6 +344,26 @@ impl ItsiResponse {
|
|
320
344
|
Ok(())
|
321
345
|
}
|
322
346
|
|
347
|
+
pub fn add_headers(&self, headers: HashMap<Bytes, Vec<Bytes>>) -> MagnusResult<()> {
|
348
|
+
if let Some(ref mut resp) = *self.data.response.write() {
|
349
|
+
let headers_mut = resp.headers_mut();
|
350
|
+
for (name, values) in headers {
|
351
|
+
let header_name = HeaderName::from_bytes(&name).map_err(|e| {
|
352
|
+
itsi_error::ItsiError::InvalidInput(format!(
|
353
|
+
"Invalid header name {:?}: {:?}",
|
354
|
+
name, e
|
355
|
+
))
|
356
|
+
})?;
|
357
|
+
for value in values {
|
358
|
+
let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
|
359
|
+
headers_mut.insert(&header_name, header_value);
|
360
|
+
}
|
361
|
+
}
|
362
|
+
}
|
363
|
+
|
364
|
+
Ok(())
|
365
|
+
}
|
366
|
+
|
323
367
|
pub fn set_status(&self, status: u16) -> MagnusResult<()> {
|
324
368
|
if let Some(ref mut resp) = *self.data.response.write() {
|
325
369
|
*resp.status_mut() = StatusCode::from_u16(status).map_err(|e| {
|
@@ -338,8 +382,8 @@ impl ItsiResponse {
|
|
338
382
|
*self.data.hijacked_socket.write() = Some(stream);
|
339
383
|
if let Some(writer) = self.data.response_writer.write().as_ref() {
|
340
384
|
writer
|
341
|
-
.blocking_send(
|
342
|
-
.map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)
|
385
|
+
.blocking_send(ByteFrame::Empty)
|
386
|
+
.map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?
|
343
387
|
}
|
344
388
|
self.close();
|
345
389
|
Ok(())
|