itsi-scheduler 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +15 -15
- data/ext/itsi_acme/Cargo.toml +1 -1
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_scheduler/extconf.rb +3 -1
- data/ext/itsi_server/Cargo.lock +1 -1
- data/ext/itsi_server/Cargo.toml +3 -1
- data/ext/itsi_server/src/lib.rs +7 -1
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +6 -6
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +71 -42
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +6 -15
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +32 -6
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
- data/ext/itsi_server/src/server/binds/listener.rs +49 -8
- data/ext/itsi_server/src/server/frame_stream.rs +142 -0
- data/ext/itsi_server/src/server/http_message_types.rs +143 -10
- data/ext/itsi_server/src/server/io_stream.rs +28 -5
- data/ext/itsi_server/src/server/lifecycle_event.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -58
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +6 -9
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +27 -42
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +65 -14
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +8 -11
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +21 -8
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +1 -5
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +13 -6
- data/ext/itsi_server/src/server/mod.rs +1 -0
- data/ext/itsi_server/src/server/process_worker.rs +5 -5
- data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +87 -31
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +1 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +166 -206
- data/ext/itsi_server/src/server/signal.rs +37 -9
- data/ext/itsi_server/src/server/thread_worker.rs +92 -70
- data/ext/itsi_server/src/services/itsi_http_service.rs +67 -62
- data/ext/itsi_server/src/services/mime_types.rs +185 -183
- data/ext/itsi_server/src/services/rate_limiter.rs +16 -34
- data/ext/itsi_server/src/services/static_file_server.rs +35 -60
- data/lib/itsi/scheduler/version.rb +1 -1
- metadata +3 -1
data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs
CHANGED
@@ -1,91 +1,89 @@
|
|
1
1
|
use super::{ContentSource, DefaultFormat, ErrorResponse};
|
2
|
-
use crate::server::http_message_types::ResponseFormat;
|
2
|
+
use crate::server::http_message_types::{HttpBody, ResponseFormat};
|
3
3
|
use bytes::Bytes;
|
4
|
-
use http_body_util::{combinators::BoxBody, Full};
|
5
|
-
use std::{convert::Infallible, sync::Arc};
|
6
4
|
|
7
5
|
impl DefaultFormat {
|
8
6
|
pub fn response_for_code(&self, code: u16) -> ContentSource {
|
9
7
|
match self {
|
10
8
|
DefaultFormat::Plaintext => match code {
|
11
|
-
500 => ContentSource::Static(
|
12
|
-
404 => ContentSource::Static(
|
13
|
-
401 => ContentSource::Static(
|
14
|
-
403 => ContentSource::Static(
|
15
|
-
413 => ContentSource::Static(
|
16
|
-
429 => ContentSource::Static(
|
17
|
-
502 => ContentSource::Static(
|
18
|
-
503 => ContentSource::Static(
|
19
|
-
504 => ContentSource::Static(
|
20
|
-
_ => ContentSource::Static(
|
9
|
+
500 => ContentSource::Static("500 Internal Error".into()),
|
10
|
+
404 => ContentSource::Static("404 Not Found".into()),
|
11
|
+
401 => ContentSource::Static("401 Unauthorized".into()),
|
12
|
+
403 => ContentSource::Static("403 Forbidden".into()),
|
13
|
+
413 => ContentSource::Static("413 Payload Too Large".into()),
|
14
|
+
429 => ContentSource::Static("429 Too Many Requests".into()),
|
15
|
+
502 => ContentSource::Static("502 Bad Gateway".into()),
|
16
|
+
503 => ContentSource::Static("503 Service Unavailable".into()),
|
17
|
+
504 => ContentSource::Static("504 Gateway Timeout".into()),
|
18
|
+
_ => ContentSource::Static("Unexpected Error".into()),
|
21
19
|
},
|
22
20
|
DefaultFormat::Html => match code {
|
23
|
-
500 => ContentSource::Static(
|
21
|
+
500 => ContentSource::Static(
|
24
22
|
include_str!("../../../../default_responses/html/500.html").into(),
|
25
|
-
)
|
26
|
-
404 => ContentSource::Static(
|
23
|
+
),
|
24
|
+
404 => ContentSource::Static(
|
27
25
|
include_str!("../../../../default_responses/html/404.html").into(),
|
28
|
-
)
|
29
|
-
401 => ContentSource::Static(
|
26
|
+
),
|
27
|
+
401 => ContentSource::Static(
|
30
28
|
include_str!("../../../../default_responses/html/401.html").into(),
|
31
|
-
)
|
32
|
-
403 => ContentSource::Static(
|
29
|
+
),
|
30
|
+
403 => ContentSource::Static(
|
33
31
|
include_str!("../../../../default_responses/html/403.html").into(),
|
34
|
-
)
|
35
|
-
413 => ContentSource::Static(
|
32
|
+
),
|
33
|
+
413 => ContentSource::Static(
|
36
34
|
include_str!("../../../../default_responses/html/413.html").into(),
|
37
|
-
)
|
38
|
-
429 => ContentSource::Static(
|
35
|
+
),
|
36
|
+
429 => ContentSource::Static(
|
39
37
|
include_str!("../../../../default_responses/html/429.html").into(),
|
40
|
-
)
|
41
|
-
502 => ContentSource::Static(
|
38
|
+
),
|
39
|
+
502 => ContentSource::Static(
|
42
40
|
include_str!("../../../../default_responses/html/502.html").into(),
|
43
|
-
)
|
44
|
-
503 => ContentSource::Static(
|
41
|
+
),
|
42
|
+
503 => ContentSource::Static(
|
45
43
|
include_str!("../../../../default_responses/html/503.html").into(),
|
46
|
-
)
|
47
|
-
504 => ContentSource::Static(
|
44
|
+
),
|
45
|
+
504 => ContentSource::Static(
|
48
46
|
include_str!("../../../../default_responses/html/504.html").into(),
|
49
|
-
)
|
50
|
-
_ => ContentSource::Static(
|
47
|
+
),
|
48
|
+
_ => ContentSource::Static(
|
51
49
|
include_str!("../../../../default_responses/html/500.html").into(),
|
52
|
-
)
|
50
|
+
),
|
53
51
|
},
|
54
52
|
DefaultFormat::Json => match code {
|
55
|
-
500 => ContentSource::Static(
|
53
|
+
500 => ContentSource::Static(
|
56
54
|
include_str!("../../../../default_responses/json/500.json").into(),
|
57
|
-
)
|
58
|
-
404 => ContentSource::Static(
|
55
|
+
),
|
56
|
+
404 => ContentSource::Static(
|
59
57
|
include_str!("../../../../default_responses/json/404.json").into(),
|
60
|
-
)
|
61
|
-
401 => ContentSource::Static(
|
58
|
+
),
|
59
|
+
401 => ContentSource::Static(
|
62
60
|
include_str!("../../../../default_responses/json/401.json").into(),
|
63
|
-
)
|
64
|
-
403 => ContentSource::Static(
|
61
|
+
),
|
62
|
+
403 => ContentSource::Static(
|
65
63
|
include_str!("../../../../default_responses/json/403.json").into(),
|
66
|
-
)
|
67
|
-
413 => ContentSource::Static(
|
64
|
+
),
|
65
|
+
413 => ContentSource::Static(
|
68
66
|
include_str!("../../../../default_responses/json/413.json").into(),
|
69
|
-
)
|
70
|
-
429 => ContentSource::Static(
|
67
|
+
),
|
68
|
+
429 => ContentSource::Static(
|
71
69
|
include_str!("../../../../default_responses/json/429.json").into(),
|
72
|
-
)
|
73
|
-
502 => ContentSource::Static(
|
70
|
+
),
|
71
|
+
502 => ContentSource::Static(
|
74
72
|
include_str!("../../../../default_responses/json/502.json").into(),
|
75
|
-
)
|
76
|
-
503 => ContentSource::Static(
|
73
|
+
),
|
74
|
+
503 => ContentSource::Static(
|
77
75
|
include_str!("../../../../default_responses/json/503.json").into(),
|
78
|
-
)
|
79
|
-
504 => ContentSource::Static(
|
76
|
+
),
|
77
|
+
504 => ContentSource::Static(
|
80
78
|
include_str!("../../../../default_responses/json/504.json").into(),
|
81
|
-
)
|
82
|
-
_ => ContentSource::Static(
|
79
|
+
),
|
80
|
+
_ => ContentSource::Static("Unexpected Error".into()),
|
83
81
|
},
|
84
82
|
}
|
85
83
|
}
|
86
84
|
}
|
87
85
|
impl ErrorResponse {
|
88
|
-
pub fn fallback_body_for(code: u16, accept: ResponseFormat) ->
|
86
|
+
pub fn fallback_body_for(code: u16, accept: ResponseFormat) -> HttpBody {
|
89
87
|
let source = match accept {
|
90
88
|
ResponseFormat::TEXT => DefaultFormat::Plaintext.response_for_code(code),
|
91
89
|
ResponseFormat::HTML => DefaultFormat::Html.response_for_code(code),
|
@@ -93,11 +91,9 @@ impl ErrorResponse {
|
|
93
91
|
ResponseFormat::UNKNOWN => ContentSource::Inline("Unexpected Error".to_owned()),
|
94
92
|
};
|
95
93
|
match source {
|
96
|
-
ContentSource::Inline(bytes) =>
|
97
|
-
ContentSource::Static(
|
98
|
-
|
99
|
-
}
|
100
|
-
ContentSource::File(_) => BoxBody::new(Full::new(Bytes::from("Unexpected error"))),
|
94
|
+
ContentSource::Inline(bytes) => HttpBody::full(Bytes::from(bytes)),
|
95
|
+
ContentSource::Static(bytes) => HttpBody::full(bytes),
|
96
|
+
ContentSource::File(_) => HttpBody::full(Bytes::from("Unexpected error")),
|
101
97
|
}
|
102
98
|
}
|
103
99
|
pub fn internal_server_error() -> Self {
|
@@ -1,14 +1,11 @@
|
|
1
1
|
use bytes::Bytes;
|
2
2
|
use http::header::CONTENT_TYPE;
|
3
3
|
use http::Response;
|
4
|
-
use http_body_util::{combinators::BoxBody, Full};
|
5
4
|
use serde::{Deserialize, Deserializer};
|
6
|
-
use std::convert::Infallible;
|
7
5
|
use std::path::PathBuf;
|
8
|
-
use std::sync::Arc;
|
9
6
|
use tracing::warn;
|
10
7
|
|
11
|
-
use crate::server::http_message_types::{HttpResponse, ResponseFormat};
|
8
|
+
use crate::server::http_message_types::{HttpBody, HttpResponse, ResponseFormat};
|
12
9
|
use crate::services::static_file_server::ROOT_STATIC_FILE_SERVER;
|
13
10
|
mod default_responses;
|
14
11
|
|
@@ -20,7 +17,7 @@ pub enum ContentSource {
|
|
20
17
|
File(PathBuf),
|
21
18
|
#[serde(rename(deserialize = "static"))]
|
22
19
|
#[serde(skip_deserializing)]
|
23
|
-
Static(
|
20
|
+
Static(Bytes),
|
24
21
|
}
|
25
22
|
|
26
23
|
#[derive(Debug, Clone, Deserialize, Default)]
|
@@ -145,19 +142,19 @@ impl ErrorResponse {
|
|
145
142
|
code: u16,
|
146
143
|
source: &Option<ContentSource>,
|
147
144
|
accept: ResponseFormat,
|
148
|
-
) ->
|
145
|
+
) -> HttpBody {
|
149
146
|
match source {
|
150
147
|
Some(ContentSource::Inline(text)) => {
|
151
|
-
return
|
148
|
+
return HttpBody::full(Bytes::from(text.clone()));
|
152
149
|
}
|
153
150
|
Some(ContentSource::Static(text)) => {
|
154
|
-
return
|
151
|
+
return HttpBody::full(text.clone());
|
155
152
|
}
|
156
153
|
Some(ContentSource::File(path)) => {
|
157
154
|
// Convert the PathBuf to a &str (assumes valid UTF-8).
|
158
155
|
if let Some(path_str) = path.to_str() {
|
159
156
|
let response = ROOT_STATIC_FILE_SERVER
|
160
|
-
.serve_single(path_str, accept
|
157
|
+
.serve_single(path_str, accept, &[])
|
161
158
|
.await;
|
162
159
|
if response.status().is_success() {
|
163
160
|
return response.into_body();
|
@@ -1,5 +1,5 @@
|
|
1
1
|
use crate::{
|
2
|
-
server::http_message_types::{HttpRequest, HttpResponse},
|
2
|
+
server::http_message_types::{HttpBody, HttpRequest, HttpResponse},
|
3
3
|
services::itsi_http_service::HttpRequestContext,
|
4
4
|
};
|
5
5
|
|
@@ -10,7 +10,7 @@ use bytes::{Bytes, BytesMut};
|
|
10
10
|
use either::Either;
|
11
11
|
use futures::TryStreamExt;
|
12
12
|
use http::{header, HeaderValue, Response, StatusCode};
|
13
|
-
use http_body_util::
|
13
|
+
use http_body_util::BodyExt;
|
14
14
|
use hyper::body::Body;
|
15
15
|
use magnus::error::Result;
|
16
16
|
use serde::Deserialize;
|
@@ -43,12 +43,6 @@ pub struct ETag {
|
|
43
43
|
pub algorithm: HashAlgorithm,
|
44
44
|
#[serde(default)]
|
45
45
|
pub min_body_size: usize,
|
46
|
-
#[serde(default = "default_true")]
|
47
|
-
pub handle_if_none_match: bool,
|
48
|
-
}
|
49
|
-
|
50
|
-
fn default_true() -> bool {
|
51
|
-
true
|
52
46
|
}
|
53
47
|
|
54
48
|
#[async_trait]
|
@@ -59,14 +53,13 @@ impl MiddlewareLayer for ETag {
|
|
59
53
|
context: &mut HttpRequestContext,
|
60
54
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
61
55
|
// Store if-none-match header in context if present for later use in after hook
|
62
|
-
if
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
context.set_if_none_match(Some(etag_value.to_string()));
|
67
|
-
}
|
56
|
+
if let Some(if_none_match) = req.headers().get(header::IF_NONE_MATCH) {
|
57
|
+
debug!(target: "middleware::etag", "Received If-None-Match header: {:?}", if_none_match);
|
58
|
+
if let Ok(etag_value) = if_none_match.to_str() {
|
59
|
+
context.set_if_none_match(Some(etag_value.to_string()));
|
68
60
|
}
|
69
61
|
}
|
62
|
+
|
70
63
|
Ok(Either::Left(req))
|
71
64
|
}
|
72
65
|
|
@@ -85,11 +78,6 @@ impl MiddlewareLayer for ETag {
|
|
85
78
|
}
|
86
79
|
}
|
87
80
|
|
88
|
-
if resp.headers().contains_key(header::ETAG) {
|
89
|
-
debug!(target: "middleware::etag", "Forwarding response with existing ETag");
|
90
|
-
return resp;
|
91
|
-
}
|
92
|
-
|
93
81
|
if let Some(cache_control) = resp.headers().get(header::CACHE_CONTROL) {
|
94
82
|
if let Ok(cache_control_str) = cache_control.to_str() {
|
95
83
|
if cache_control_str.contains("no-store") {
|
@@ -125,7 +113,7 @@ impl MiddlewareLayer for ETag {
|
|
125
113
|
.await
|
126
114
|
{
|
127
115
|
Ok(bytes_mut) => bytes_mut.freeze(),
|
128
|
-
Err(_) => return Response::from_parts(parts,
|
116
|
+
Err(_) => return Response::from_parts(parts, HttpBody::empty()),
|
129
117
|
};
|
130
118
|
|
131
119
|
let computed_etag = match self.algorithm {
|
@@ -151,32 +139,30 @@ impl MiddlewareLayer for ETag {
|
|
151
139
|
parts.headers.insert(header::ETAG, value);
|
152
140
|
}
|
153
141
|
|
154
|
-
body =
|
142
|
+
body = HttpBody::full(full_bytes);
|
155
143
|
formatted_etag
|
156
144
|
};
|
157
145
|
|
158
|
-
if
|
159
|
-
if
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
&header::
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
)
|
175
|
-
not_modified.headers_mut().insert(name, value.clone());
|
176
|
-
}
|
146
|
+
if let Some(if_none_match) = context.get_if_none_match() {
|
147
|
+
if if_none_match == etag_value || if_none_match == "*" {
|
148
|
+
// Return 304 Not Modified without the body
|
149
|
+
let mut not_modified = Response::new(HttpBody::empty());
|
150
|
+
*not_modified.status_mut() = StatusCode::NOT_MODIFIED;
|
151
|
+
// Copy headers we want to preserve
|
152
|
+
for (name, value) in parts.headers.iter() {
|
153
|
+
if matches!(
|
154
|
+
name,
|
155
|
+
&header::CACHE_CONTROL
|
156
|
+
| &header::CONTENT_LOCATION
|
157
|
+
| &header::DATE
|
158
|
+
| &header::ETAG
|
159
|
+
| &header::EXPIRES
|
160
|
+
| &header::VARY
|
161
|
+
) {
|
162
|
+
not_modified.headers_mut().insert(name, value.clone());
|
177
163
|
}
|
178
|
-
return not_modified;
|
179
164
|
}
|
165
|
+
return not_modified;
|
180
166
|
}
|
181
167
|
}
|
182
168
|
|
@@ -190,7 +176,6 @@ impl Default for ETag {
|
|
190
176
|
r#type: ETagType::Strong,
|
191
177
|
algorithm: HashAlgorithm::Sha256,
|
192
178
|
min_body_size: 0,
|
193
|
-
handle_if_none_match: true,
|
194
179
|
}
|
195
180
|
}
|
196
181
|
}
|
@@ -3,6 +3,7 @@ use either::Either;
|
|
3
3
|
use itsi_tracing::*;
|
4
4
|
use magnus::error::Result;
|
5
5
|
use serde::Deserialize;
|
6
|
+
use tracing::enabled;
|
6
7
|
|
7
8
|
use crate::server::http_message_types::{HttpRequest, HttpResponse};
|
8
9
|
use crate::services::itsi_http_service::HttpRequestContext;
|
@@ -39,18 +40,6 @@ pub enum LogMiddlewareLevel {
|
|
39
40
|
Error,
|
40
41
|
}
|
41
42
|
|
42
|
-
impl LogMiddlewareLevel {
|
43
|
-
pub fn log(&self, message: String) {
|
44
|
-
match self {
|
45
|
-
LogMiddlewareLevel::Trace => trace!(target: "middleware::log_requests", message),
|
46
|
-
LogMiddlewareLevel::Debug => debug!(target: "middleware::log_requests", message),
|
47
|
-
LogMiddlewareLevel::Info => info!(target: "middleware::log_requests", message),
|
48
|
-
LogMiddlewareLevel::Warn => warn!(target: "middleware::log_requests", message),
|
49
|
-
LogMiddlewareLevel::Error => error!(target: "middleware::log_requests", message),
|
50
|
-
}
|
51
|
-
}
|
52
|
-
}
|
53
|
-
|
54
43
|
#[async_trait]
|
55
44
|
impl MiddlewareLayer for LogRequests {
|
56
45
|
async fn initialize(&self) -> Result<()> {
|
@@ -64,7 +53,38 @@ impl MiddlewareLayer for LogRequests {
|
|
64
53
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
65
54
|
context.init_logging_params();
|
66
55
|
if let Some(LogConfig { level, format }) = self.before.as_ref() {
|
67
|
-
level
|
56
|
+
match level {
|
57
|
+
LogMiddlewareLevel::Trace => {
|
58
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::TRACE) {
|
59
|
+
let message = format.rewrite_request(&req, context);
|
60
|
+
trace!(target: "middleware::log_requests", message);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
LogMiddlewareLevel::Debug => {
|
64
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::DEBUG) {
|
65
|
+
let message = format.rewrite_request(&req, context);
|
66
|
+
debug!(target: "middleware::log_requests", message);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
LogMiddlewareLevel::Info => {
|
70
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::INFO) {
|
71
|
+
let message = format.rewrite_request(&req, context);
|
72
|
+
info!(target: "middleware::log_requests", message);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
LogMiddlewareLevel::Warn => {
|
76
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::WARN) {
|
77
|
+
let message = format.rewrite_request(&req, context);
|
78
|
+
warn!(target: "middleware::log_requests", message);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
LogMiddlewareLevel::Error => {
|
82
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::ERROR) {
|
83
|
+
let message = format.rewrite_request(&req, context);
|
84
|
+
error!(target: "middleware::log_requests", message);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
68
88
|
}
|
69
89
|
|
70
90
|
Ok(Either::Left(req))
|
@@ -72,7 +92,38 @@ impl MiddlewareLayer for LogRequests {
|
|
72
92
|
|
73
93
|
async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
|
74
94
|
if let Some(LogConfig { level, format }) = self.after.as_ref() {
|
75
|
-
level
|
95
|
+
match level {
|
96
|
+
LogMiddlewareLevel::Trace => {
|
97
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::TRACE) {
|
98
|
+
let message = format.rewrite_response(&resp, context);
|
99
|
+
trace!(target: "middleware::log_requests", message);
|
100
|
+
}
|
101
|
+
}
|
102
|
+
LogMiddlewareLevel::Debug => {
|
103
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::DEBUG) {
|
104
|
+
let message = format.rewrite_response(&resp, context);
|
105
|
+
debug!(target: "middleware::log_requests", message);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
LogMiddlewareLevel::Info => {
|
109
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::INFO) {
|
110
|
+
let message = format.rewrite_response(&resp, context);
|
111
|
+
info!(target: "middleware::log_requests", message);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
LogMiddlewareLevel::Warn => {
|
115
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::WARN) {
|
116
|
+
let message = format.rewrite_response(&resp, context);
|
117
|
+
warn!(target: "middleware::log_requests", message);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
LogMiddlewareLevel::Error => {
|
121
|
+
if enabled!(target: "middleware::log_requests", tracing::Level::ERROR) {
|
122
|
+
let message = format.rewrite_response(&resp, context);
|
123
|
+
error!(target: "middleware::log_requests", message);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
76
127
|
}
|
77
128
|
|
78
129
|
resp
|
@@ -37,7 +37,7 @@ impl MiddlewareLayer for MaxBody {
|
|
37
37
|
async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
|
38
38
|
if resp.status() == StatusCode::PAYLOAD_TOO_LARGE {
|
39
39
|
self.error_response
|
40
|
-
.to_http_response(context.response_format()
|
40
|
+
.to_http_response(*context.response_format())
|
41
41
|
.await
|
42
42
|
} else {
|
43
43
|
resp
|
@@ -14,7 +14,7 @@ use super::{string_rewrite::StringRewrite, ErrorResponse, FromValue, MiddlewareL
|
|
14
14
|
use crate::{
|
15
15
|
server::{
|
16
16
|
binds::bind::{Bind, BindAddress},
|
17
|
-
http_message_types::{HttpRequest, HttpResponse, RequestExt, ResponseFormat},
|
17
|
+
http_message_types::{HttpBody, HttpRequest, HttpResponse, RequestExt, ResponseFormat},
|
18
18
|
size_limited_incoming::MaxBodySizeReached,
|
19
19
|
},
|
20
20
|
services::itsi_http_service::HttpRequestContext,
|
@@ -24,8 +24,7 @@ use bytes::{Bytes, BytesMut};
|
|
24
24
|
use either::Either;
|
25
25
|
use futures::TryStreamExt;
|
26
26
|
use http::{HeaderMap, Method, Response, StatusCode};
|
27
|
-
use http_body_util::
|
28
|
-
use hyper::body::Frame;
|
27
|
+
use http_body_util::BodyExt;
|
29
28
|
use magnus::error::Result;
|
30
29
|
use rand::Rng;
|
31
30
|
use reqwest::{
|
@@ -303,7 +302,7 @@ impl MiddlewareLayer for Proxy {
|
|
303
302
|
let url = self.to.rewrite_request(&req, context);
|
304
303
|
|
305
304
|
let accept: ResponseFormat = req.accept().into();
|
306
|
-
let error_response = self.error_response.to_http_response(accept
|
305
|
+
let error_response = self.error_response.to_http_response(accept).await;
|
307
306
|
|
308
307
|
let destination = match Url::parse(&url) {
|
309
308
|
Ok(dest) => dest,
|
@@ -373,19 +372,17 @@ impl MiddlewareLayer for Proxy {
|
|
373
372
|
for (hn, hv) in response.headers() {
|
374
373
|
builder = builder.header(hn, hv);
|
375
374
|
}
|
376
|
-
let response =
|
377
|
-
response
|
378
|
-
|
379
|
-
|
380
|
-
.map_err(|_| -> Infallible { unreachable!("We handle IO errors above") }),
|
381
|
-
)));
|
375
|
+
let response =
|
376
|
+
builder.body(HttpBody::stream(response.bytes_stream().map_err(
|
377
|
+
|_| -> Infallible { unreachable!("We handle IO errors above") },
|
378
|
+
)));
|
382
379
|
response.unwrap_or(error_response)
|
383
380
|
}
|
384
381
|
Err(e) => {
|
385
382
|
debug!(target: "middleware::proxy", "Error {:?} received", e);
|
386
383
|
if let Some(inner) = e.source() {
|
387
384
|
if inner.downcast_ref::<MaxBodySizeReached>().is_some() {
|
388
|
-
let mut max_body_response = Response::new(
|
385
|
+
let mut max_body_response = Response::new(HttpBody::empty());
|
389
386
|
*max_body_response.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
|
390
387
|
return Ok(Either::Right(max_body_response));
|
391
388
|
}
|
@@ -6,6 +6,7 @@ use crate::services::rate_limiter::{
|
|
6
6
|
};
|
7
7
|
use async_trait::async_trait;
|
8
8
|
use either::Either;
|
9
|
+
use http::{HeaderName, HeaderValue};
|
9
10
|
use magnus::error::Result;
|
10
11
|
use serde::Deserialize;
|
11
12
|
use std::collections::HashMap;
|
@@ -24,6 +25,8 @@ pub struct RateLimit {
|
|
24
25
|
pub trusted_proxies: HashMap<String, TokenSource>,
|
25
26
|
#[serde(default = "too_many_requests_error_response")]
|
26
27
|
pub error_response: ErrorResponse,
|
28
|
+
#[serde(skip)]
|
29
|
+
pub limit_header_value: OnceLock<HeaderValue>,
|
27
30
|
}
|
28
31
|
|
29
32
|
fn too_many_requests_error_response() -> ErrorResponse {
|
@@ -38,6 +41,12 @@ pub enum RateLimitKey {
|
|
38
41
|
Parameter(TokenSource),
|
39
42
|
}
|
40
43
|
|
44
|
+
static X_RATELIMIT_LIMIT: HeaderName = HeaderName::from_static("x-ratelimit-limit");
|
45
|
+
static X_RATELIMIT_REMAINING: HeaderName = HeaderName::from_static("x-ratelimit-remaining");
|
46
|
+
static X_RATELIMIT_RESET: HeaderName = HeaderName::from_static("x-ratelimit-reset");
|
47
|
+
static RETRY_AFTER: HeaderName = HeaderName::from_static("retry-after");
|
48
|
+
static ZERO_VALUE: HeaderValue = HeaderValue::from_static("0");
|
49
|
+
|
41
50
|
#[async_trait]
|
42
51
|
impl MiddlewareLayer for RateLimit {
|
43
52
|
async fn initialize(&self) -> Result<()> {
|
@@ -46,6 +55,9 @@ impl MiddlewareLayer for RateLimit {
|
|
46
55
|
if let Ok(limiter) = get_rate_limiter(&self.store_config).await {
|
47
56
|
let _ = self.rate_limiter.set(limiter);
|
48
57
|
}
|
58
|
+
self.limit_header_value
|
59
|
+
.set(self.requests.to_string().parse().unwrap())
|
60
|
+
.ok();
|
49
61
|
Ok(())
|
50
62
|
}
|
51
63
|
|
@@ -58,8 +70,7 @@ impl MiddlewareLayer for RateLimit {
|
|
58
70
|
let key_value = match &self.key {
|
59
71
|
RateLimitKey::SocketAddress => {
|
60
72
|
// Use the socket address from the context
|
61
|
-
if self.trusted_proxies.
|
62
|
-
let source = self.trusted_proxies.get(&context.addr).unwrap();
|
73
|
+
if let Some(source) = self.trusted_proxies.get(&context.addr) {
|
63
74
|
source.extract_token(&req).unwrap_or(&context.addr)
|
64
75
|
} else {
|
65
76
|
&context.addr
|
@@ -114,18 +125,20 @@ impl MiddlewareLayer for RateLimit {
|
|
114
125
|
.error_response
|
115
126
|
.to_http_response(req.accept().into())
|
116
127
|
.await;
|
128
|
+
let ttl_header_value: HeaderValue = ttl.to_string().parse().unwrap();
|
129
|
+
response.headers_mut().insert(
|
130
|
+
X_RATELIMIT_LIMIT.clone(),
|
131
|
+
self.limit_header_value.get().unwrap().clone(),
|
132
|
+
);
|
117
133
|
response
|
118
134
|
.headers_mut()
|
119
|
-
.insert(
|
120
|
-
response
|
121
|
-
.headers_mut()
|
122
|
-
.insert("X-RateLimit-Remaining", "0".parse().unwrap());
|
135
|
+
.insert(X_RATELIMIT_REMAINING.clone(), ZERO_VALUE.clone());
|
123
136
|
response
|
124
137
|
.headers_mut()
|
125
|
-
.insert(
|
138
|
+
.insert(X_RATELIMIT_RESET.clone(), ttl_header_value.clone());
|
126
139
|
response
|
127
140
|
.headers_mut()
|
128
|
-
.insert(
|
141
|
+
.insert(RETRY_AFTER.clone(), ttl_header_value);
|
129
142
|
Ok(Either::Right(response))
|
130
143
|
}
|
131
144
|
Err(e) => {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
|
2
2
|
use crate::{
|
3
3
|
server::{
|
4
|
-
http_message_types::{HttpRequest, HttpResponse},
|
4
|
+
http_message_types::{HttpBody, HttpRequest, HttpResponse},
|
5
5
|
redirect_type::RedirectType,
|
6
6
|
},
|
7
7
|
services::itsi_http_service::HttpRequestContext,
|
@@ -9,7 +9,6 @@ use crate::{
|
|
9
9
|
use async_trait::async_trait;
|
10
10
|
use either::Either;
|
11
11
|
use http::Response;
|
12
|
-
use http_body_util::{combinators::BoxBody, Empty};
|
13
12
|
use magnus::error::Result;
|
14
13
|
use serde::Deserialize;
|
15
14
|
use tracing::debug;
|
@@ -39,7 +38,7 @@ impl Redirect {
|
|
39
38
|
req: &HttpRequest,
|
40
39
|
context: &mut HttpRequestContext,
|
41
40
|
) -> Result<HttpResponse> {
|
42
|
-
let mut response = Response::new(
|
41
|
+
let mut response = Response::new(HttpBody::empty());
|
43
42
|
*response.status_mut() = self.redirect_type.status_code();
|
44
43
|
let destination = self.to.rewrite_request(req, context).parse().map_err(|e| {
|
45
44
|
magnus::Error::new(
|
@@ -119,11 +119,7 @@ impl MiddlewareLayer for RubyApp {
|
|
119
119
|
if self.sendfile {
|
120
120
|
if let Some(sendfile_header) = resp.headers().get("X-Sendfile") {
|
121
121
|
return ROOT_STATIC_FILE_SERVER
|
122
|
-
.serve_single_abs(
|
123
|
-
sendfile_header.to_str().unwrap(),
|
124
|
-
context.accept.clone(),
|
125
|
-
&[],
|
126
|
-
)
|
122
|
+
.serve_single_abs(sendfile_header.to_str().unwrap(), context.accept, &[])
|
127
123
|
.await;
|
128
124
|
}
|
129
125
|
}
|
@@ -134,8 +134,7 @@ impl MiddlewareLayer for StaticAssets {
|
|
134
134
|
let file_server = self.file_server.get().unwrap();
|
135
135
|
let encodings: &[HeaderValue] = context
|
136
136
|
.supported_encoding_set()
|
137
|
-
.
|
138
|
-
.unwrap_or(&[] as &[HeaderValue]);
|
137
|
+
.map_or(&[], |set| set.as_slice());
|
139
138
|
let response = file_server
|
140
139
|
.serve(
|
141
140
|
&req,
|