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,414 @@
|
|
1
|
+
use std::{
|
2
|
+
collections::HashMap,
|
3
|
+
convert::Infallible,
|
4
|
+
error::Error,
|
5
|
+
net::SocketAddr,
|
6
|
+
sync::{
|
7
|
+
atomic::{AtomicUsize, Ordering},
|
8
|
+
Arc, LazyLock, OnceLock,
|
9
|
+
},
|
10
|
+
time::Duration,
|
11
|
+
};
|
12
|
+
|
13
|
+
use super::{string_rewrite::StringRewrite, ErrorResponse, FromValue, MiddlewareLayer};
|
14
|
+
use crate::{
|
15
|
+
server::{
|
16
|
+
binds::bind::{Bind, BindAddress},
|
17
|
+
http_message_types::{HttpRequest, HttpResponse, RequestExt, ResponseFormat},
|
18
|
+
size_limited_incoming::MaxBodySizeReached,
|
19
|
+
},
|
20
|
+
services::itsi_http_service::HttpRequestContext,
|
21
|
+
};
|
22
|
+
use async_trait::async_trait;
|
23
|
+
use bytes::{Bytes, BytesMut};
|
24
|
+
use either::Either;
|
25
|
+
use futures::TryStreamExt;
|
26
|
+
use http::{HeaderMap, Method, Response, StatusCode};
|
27
|
+
use http_body_util::{combinators::BoxBody, BodyExt, Empty, StreamBody};
|
28
|
+
use hyper::body::Frame;
|
29
|
+
use magnus::error::Result;
|
30
|
+
use rand::Rng;
|
31
|
+
use reqwest::{
|
32
|
+
dns::{Name, Resolve},
|
33
|
+
Body, Client, Url,
|
34
|
+
};
|
35
|
+
use serde::Deserialize;
|
36
|
+
|
37
|
+
#[derive(Debug, Clone, Deserialize)]
|
38
|
+
pub struct Proxy {
|
39
|
+
pub to: StringRewrite,
|
40
|
+
pub backends: Vec<String>,
|
41
|
+
pub backend_priority: BackendPriority,
|
42
|
+
pub headers: HashMap<String, Option<ProxiedHeader>>,
|
43
|
+
pub verify_ssl: bool,
|
44
|
+
pub timeout: u64,
|
45
|
+
pub tls_sni: bool,
|
46
|
+
#[serde(skip_deserializing)]
|
47
|
+
pub client: OnceLock<Client>,
|
48
|
+
#[serde(default = "bad_gateway_error_response")]
|
49
|
+
pub error_response: ErrorResponse,
|
50
|
+
}
|
51
|
+
|
52
|
+
fn bad_gateway_error_response() -> ErrorResponse {
|
53
|
+
ErrorResponse::bad_gateway()
|
54
|
+
}
|
55
|
+
|
56
|
+
#[derive(Debug, Clone, Deserialize)]
|
57
|
+
pub enum BackendPriority {
|
58
|
+
#[serde(rename(deserialize = "round_robin"))]
|
59
|
+
RoundRobin,
|
60
|
+
#[serde(rename(deserialize = "ordered"))]
|
61
|
+
Ordered,
|
62
|
+
#[serde(rename(deserialize = "random"))]
|
63
|
+
Random,
|
64
|
+
}
|
65
|
+
|
66
|
+
#[derive(Debug, Clone, Deserialize)]
|
67
|
+
pub enum ProxiedHeader {
|
68
|
+
#[serde(rename(deserialize = "value"))]
|
69
|
+
String(String),
|
70
|
+
#[serde(rename(deserialize = "rewrite"))]
|
71
|
+
StringRewrite(StringRewrite),
|
72
|
+
}
|
73
|
+
|
74
|
+
impl ProxiedHeader {
|
75
|
+
pub fn to_string(&self, req: &HttpRequest, context: &HttpRequestContext) -> String {
|
76
|
+
match self {
|
77
|
+
ProxiedHeader::String(value) => value.clone(),
|
78
|
+
ProxiedHeader::StringRewrite(rewrite) => rewrite.rewrite_request(req, context),
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
#[derive(Debug, Clone)]
|
84
|
+
pub struct Resolver {
|
85
|
+
backends: Arc<Vec<SocketAddr>>,
|
86
|
+
counter: Arc<AtomicUsize>,
|
87
|
+
backend_priority: BackendPriority,
|
88
|
+
}
|
89
|
+
|
90
|
+
pub struct StatefulResolverIter {
|
91
|
+
backends: Arc<Vec<SocketAddr>>,
|
92
|
+
start_index: usize,
|
93
|
+
current: usize,
|
94
|
+
}
|
95
|
+
|
96
|
+
impl Iterator for StatefulResolverIter {
|
97
|
+
type Item = SocketAddr;
|
98
|
+
|
99
|
+
fn next(&mut self) -> Option<Self::Item> {
|
100
|
+
if self.current < self.backends.len() {
|
101
|
+
let index = (self.start_index + self.current) % self.backends.len();
|
102
|
+
self.current += 1;
|
103
|
+
Some(self.backends[index])
|
104
|
+
} else {
|
105
|
+
None
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
impl Resolve for Resolver {
|
111
|
+
fn resolve(&self, _name: Name) -> reqwest::dns::Resolving {
|
112
|
+
let backends = self.backends.clone();
|
113
|
+
let len = backends.len();
|
114
|
+
|
115
|
+
let start_index = match self.backend_priority {
|
116
|
+
BackendPriority::Ordered => 0,
|
117
|
+
BackendPriority::Random => rand::rng().random_range(0..len),
|
118
|
+
BackendPriority::RoundRobin => self.counter.fetch_add(1, Ordering::Relaxed) % len,
|
119
|
+
};
|
120
|
+
|
121
|
+
let fut = async move {
|
122
|
+
let iter = StatefulResolverIter {
|
123
|
+
backends,
|
124
|
+
start_index,
|
125
|
+
current: 0,
|
126
|
+
};
|
127
|
+
Ok(Box::new(iter) as Box<dyn Iterator<Item = SocketAddr> + Send>)
|
128
|
+
};
|
129
|
+
|
130
|
+
Box::pin(fut)
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
static BAD_GATEWAY_RESPONSE: LazyLock<ErrorResponse> = LazyLock::new(ErrorResponse::bad_gateway);
|
135
|
+
static GATEWAY_TIMEOUT_RESPONSE: LazyLock<ErrorResponse> =
|
136
|
+
LazyLock::new(ErrorResponse::gateway_timeout);
|
137
|
+
static SERVICE_UNAVAILABLE_RESPONSE: LazyLock<ErrorResponse> =
|
138
|
+
LazyLock::new(ErrorResponse::service_unavailable);
|
139
|
+
static INTERNAL_SERVER_ERROR_RESPONSE: LazyLock<ErrorResponse> =
|
140
|
+
LazyLock::new(ErrorResponse::internal_server_error);
|
141
|
+
|
142
|
+
fn is_idempotent(method: &Method) -> bool {
|
143
|
+
matches!(
|
144
|
+
*method,
|
145
|
+
Method::GET | Method::HEAD | Method::PUT | Method::DELETE | Method::OPTIONS
|
146
|
+
)
|
147
|
+
}
|
148
|
+
|
149
|
+
/// A helper that stores the immutable parts of the incoming request.
|
150
|
+
struct RequestInfo {
|
151
|
+
method: Method,
|
152
|
+
headers: HeaderMap,
|
153
|
+
}
|
154
|
+
|
155
|
+
impl Proxy {
|
156
|
+
/// Build a header map of overriding headers based on the configured values.
|
157
|
+
/// This uses the full HttpRequest and context to compute each header value.
|
158
|
+
fn build_overriding_headers(
|
159
|
+
&self,
|
160
|
+
req: &HttpRequest,
|
161
|
+
context: &mut HttpRequestContext,
|
162
|
+
) -> http::HeaderMap {
|
163
|
+
let mut headers = http::HeaderMap::new();
|
164
|
+
for (name, header_opt) in self.headers.iter() {
|
165
|
+
if let Some(header_value) = header_opt {
|
166
|
+
// Compute the header value using the full HttpRequest.
|
167
|
+
let value_str = header_value.to_string(req, context);
|
168
|
+
if let Ok(header_val) = http::HeaderValue::from_str(&value_str) {
|
169
|
+
if let Ok(header_name) = name.parse::<http::header::HeaderName>() {
|
170
|
+
headers.insert(header_name, header_val);
|
171
|
+
}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
headers
|
176
|
+
}
|
177
|
+
|
178
|
+
/// Build a reqwest::RequestBuilder by merging the original request headers
|
179
|
+
/// (unless overridden) with the precomputed overriding headers.
|
180
|
+
fn build_reqwest_request_info(
|
181
|
+
&self,
|
182
|
+
req_info: &RequestInfo,
|
183
|
+
url: &str,
|
184
|
+
host_str: &str,
|
185
|
+
body: Body,
|
186
|
+
overriding_headers: &http::HeaderMap,
|
187
|
+
) -> reqwest::RequestBuilder {
|
188
|
+
let mut builder = self
|
189
|
+
.client
|
190
|
+
.get()
|
191
|
+
.unwrap()
|
192
|
+
.request(req_info.method.clone(), url);
|
193
|
+
|
194
|
+
// Forward headers from the original request unless they are overridden.
|
195
|
+
for (name, value) in req_info.headers.iter() {
|
196
|
+
if overriding_headers.contains_key(name) {
|
197
|
+
continue;
|
198
|
+
}
|
199
|
+
builder = builder.header(name, value);
|
200
|
+
}
|
201
|
+
|
202
|
+
// Add a Host header if not overridden.
|
203
|
+
if !overriding_headers.contains_key("host") && !host_str.is_empty() {
|
204
|
+
builder = builder.header("Host", host_str);
|
205
|
+
}
|
206
|
+
|
207
|
+
for (name, value) in overriding_headers.iter() {
|
208
|
+
builder = builder.header(name, value);
|
209
|
+
}
|
210
|
+
|
211
|
+
builder.body(body)
|
212
|
+
}
|
213
|
+
|
214
|
+
/// Sends an idempotent request using a replayable (buffered) body.
|
215
|
+
async fn send_request_idempotent(
|
216
|
+
&self,
|
217
|
+
req_info: &RequestInfo,
|
218
|
+
url: &str,
|
219
|
+
host_str: &str,
|
220
|
+
max_attempts: usize,
|
221
|
+
replayable_bytes: Bytes,
|
222
|
+
overriding_headers: &http::HeaderMap,
|
223
|
+
) -> std::result::Result<reqwest::Response, reqwest::Error> {
|
224
|
+
let mut last_err = None;
|
225
|
+
for attempt in 0..max_attempts {
|
226
|
+
let body = Body::from(replayable_bytes.clone());
|
227
|
+
let builder =
|
228
|
+
self.build_reqwest_request_info(req_info, url, host_str, body, overriding_headers);
|
229
|
+
match builder.send().await {
|
230
|
+
Ok(response) => return Ok(response),
|
231
|
+
Err(e) => {
|
232
|
+
// Retry for connectivity-related errors.
|
233
|
+
if e.is_connect() {
|
234
|
+
last_err = Some(e);
|
235
|
+
if attempt + 1 < max_attempts {
|
236
|
+
continue;
|
237
|
+
} else {
|
238
|
+
break;
|
239
|
+
}
|
240
|
+
} else {
|
241
|
+
return Err(e);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
245
|
+
}
|
246
|
+
Err(last_err.expect("At least one attempt should have set last_err"))
|
247
|
+
}
|
248
|
+
|
249
|
+
/// Sends a non-idempotent request once using its streaming body.
|
250
|
+
async fn send_request_non_idempotent(
|
251
|
+
&self,
|
252
|
+
req: HttpRequest,
|
253
|
+
req_info: &RequestInfo,
|
254
|
+
url: &str,
|
255
|
+
host_str: &str,
|
256
|
+
overriding_headers: &http::HeaderMap,
|
257
|
+
) -> std::result::Result<reqwest::Response, reqwest::Error> {
|
258
|
+
let body = Body::wrap_stream(req.into_data_stream());
|
259
|
+
let builder =
|
260
|
+
self.build_reqwest_request_info(req_info, url, host_str, body, overriding_headers);
|
261
|
+
builder.send().await
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
#[async_trait]
|
266
|
+
impl MiddlewareLayer for Proxy {
|
267
|
+
async fn initialize(&self) -> Result<()> {
|
268
|
+
let backends = self
|
269
|
+
.backends
|
270
|
+
.iter()
|
271
|
+
.filter_map(|be| {
|
272
|
+
let bind: Bind = be.parse().ok()?;
|
273
|
+
match (bind.address, bind.port) {
|
274
|
+
(BindAddress::Ip(ip_addr), port) => {
|
275
|
+
Some(SocketAddr::new(ip_addr, port.unwrap()))
|
276
|
+
}
|
277
|
+
(BindAddress::UnixSocket(_), _) => None,
|
278
|
+
}
|
279
|
+
})
|
280
|
+
.collect::<Vec<_>>();
|
281
|
+
|
282
|
+
self.client
|
283
|
+
.set(
|
284
|
+
Client::builder()
|
285
|
+
.timeout(Duration::from_secs(self.timeout))
|
286
|
+
.danger_accept_invalid_certs(!self.verify_ssl)
|
287
|
+
.danger_accept_invalid_hostnames(!self.verify_ssl)
|
288
|
+
.dns_resolver(Arc::new(Resolver {
|
289
|
+
backends: Arc::new(backends),
|
290
|
+
counter: Arc::new(AtomicUsize::new(0)),
|
291
|
+
backend_priority: self.backend_priority.clone(),
|
292
|
+
}))
|
293
|
+
.tls_sni(self.tls_sni)
|
294
|
+
.build()
|
295
|
+
.map_err(|e| {
|
296
|
+
magnus::Error::new(
|
297
|
+
magnus::exception::runtime_error(),
|
298
|
+
format!("Failed to build Reqwest client: {}", e),
|
299
|
+
)
|
300
|
+
})?,
|
301
|
+
)
|
302
|
+
.map_err(|_e| {
|
303
|
+
magnus::Error::new(
|
304
|
+
magnus::exception::standard_error(),
|
305
|
+
"Failed to save resolver backends",
|
306
|
+
)
|
307
|
+
})?;
|
308
|
+
Ok(())
|
309
|
+
}
|
310
|
+
|
311
|
+
async fn before(
|
312
|
+
&self,
|
313
|
+
req: HttpRequest,
|
314
|
+
context: &mut HttpRequestContext,
|
315
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
316
|
+
let url = self.to.rewrite_request(&req, context);
|
317
|
+
let accept: ResponseFormat = req.accept().into();
|
318
|
+
let error_response = self.error_response.to_http_response(accept.clone()).await;
|
319
|
+
|
320
|
+
let destination = match Url::parse(&url) {
|
321
|
+
Ok(dest) => dest,
|
322
|
+
Err(_) => return Ok(Either::Right(error_response)),
|
323
|
+
};
|
324
|
+
|
325
|
+
// Clone the headers before consuming the request.
|
326
|
+
let req_headers = req.headers().clone();
|
327
|
+
let host_str = destination.host_str().unwrap_or_else(|| {
|
328
|
+
req_headers
|
329
|
+
.get("Host")
|
330
|
+
.and_then(|h| h.to_str().ok())
|
331
|
+
.unwrap_or("")
|
332
|
+
});
|
333
|
+
|
334
|
+
let req_info = RequestInfo {
|
335
|
+
method: req.method().clone(),
|
336
|
+
headers: req_headers.clone(),
|
337
|
+
};
|
338
|
+
|
339
|
+
// Precompute the overriding headers from the full request.
|
340
|
+
let overriding_headers = self.build_overriding_headers(&req, context);
|
341
|
+
|
342
|
+
// Determine max_attempts based on the number of backends.
|
343
|
+
let max_attempts = self.backends.len();
|
344
|
+
|
345
|
+
let reqwest_response_result = if is_idempotent(&req_info.method) {
|
346
|
+
let (_parts, body) = req.into_parts();
|
347
|
+
let replayable_bytes = match body.into_data_stream().try_collect::<Vec<Bytes>>().await {
|
348
|
+
Ok(chunks) => {
|
349
|
+
let mut buf = BytesMut::new();
|
350
|
+
for chunk in chunks {
|
351
|
+
buf.extend_from_slice(&chunk);
|
352
|
+
}
|
353
|
+
buf.freeze()
|
354
|
+
}
|
355
|
+
Err(e) => {
|
356
|
+
tracing::error!("Error buffering request body: {}", e);
|
357
|
+
return Ok(Either::Right(error_response));
|
358
|
+
}
|
359
|
+
};
|
360
|
+
self.send_request_idempotent(
|
361
|
+
&req_info,
|
362
|
+
&url,
|
363
|
+
host_str,
|
364
|
+
max_attempts,
|
365
|
+
replayable_bytes,
|
366
|
+
&overriding_headers,
|
367
|
+
)
|
368
|
+
.await
|
369
|
+
} else {
|
370
|
+
self.send_request_non_idempotent(req, &req_info, &url, host_str, &overriding_headers)
|
371
|
+
.await
|
372
|
+
};
|
373
|
+
|
374
|
+
let response = match reqwest_response_result {
|
375
|
+
Ok(response) => {
|
376
|
+
let status = response.status();
|
377
|
+
let mut builder = Response::builder().status(status);
|
378
|
+
for (hn, hv) in response.headers() {
|
379
|
+
builder = builder.header(hn, hv);
|
380
|
+
}
|
381
|
+
let response = builder.body(BoxBody::new(StreamBody::new(
|
382
|
+
response
|
383
|
+
.bytes_stream()
|
384
|
+
.map_ok(Frame::data)
|
385
|
+
.map_err(|_| -> Infallible { unreachable!("We handle IO errors above") }),
|
386
|
+
)));
|
387
|
+
response.unwrap_or(error_response)
|
388
|
+
}
|
389
|
+
Err(e) => {
|
390
|
+
if let Some(inner) = e.source() {
|
391
|
+
if inner.downcast_ref::<MaxBodySizeReached>().is_some() {
|
392
|
+
let mut max_body_response = Response::new(BoxBody::new(Empty::new()));
|
393
|
+
*max_body_response.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
|
394
|
+
return Ok(Either::Right(max_body_response));
|
395
|
+
}
|
396
|
+
}
|
397
|
+
if e.is_timeout() {
|
398
|
+
GATEWAY_TIMEOUT_RESPONSE.to_http_response(accept).await
|
399
|
+
} else if e.is_connect() {
|
400
|
+
BAD_GATEWAY_RESPONSE.to_http_response(accept).await
|
401
|
+
} else if e.is_status() {
|
402
|
+
SERVICE_UNAVAILABLE_RESPONSE.to_http_response(accept).await
|
403
|
+
} else {
|
404
|
+
INTERNAL_SERVER_ERROR_RESPONSE
|
405
|
+
.to_http_response(accept)
|
406
|
+
.await
|
407
|
+
}
|
408
|
+
}
|
409
|
+
};
|
410
|
+
|
411
|
+
Ok(Either::Right(response))
|
412
|
+
}
|
413
|
+
}
|
414
|
+
impl FromValue for Proxy {}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
use super::{token_source::TokenSource, ErrorResponse, FromValue, MiddlewareLayer};
|
2
|
+
use crate::server::http_message_types::{HttpRequest, HttpResponse, RequestExt};
|
3
|
+
use crate::services::itsi_http_service::HttpRequestContext;
|
4
|
+
use crate::services::rate_limiter::{
|
5
|
+
create_rate_limit_key, get_rate_limiter, RateLimitError, RateLimiter, RateLimiterConfig,
|
6
|
+
};
|
7
|
+
use async_trait::async_trait;
|
8
|
+
use either::Either;
|
9
|
+
use magnus::error::Result;
|
10
|
+
use serde::Deserialize;
|
11
|
+
use std::sync::{Arc, OnceLock};
|
12
|
+
use std::time::Duration;
|
13
|
+
|
14
|
+
#[derive(Debug, Clone, Deserialize)]
|
15
|
+
pub struct RateLimit {
|
16
|
+
pub requests: u64,
|
17
|
+
pub seconds: u64,
|
18
|
+
pub key: RateLimitKey,
|
19
|
+
#[serde(skip_deserializing)]
|
20
|
+
pub rate_limiter: OnceLock<Arc<dyn RateLimiter>>,
|
21
|
+
pub store_config: RateLimiterConfig,
|
22
|
+
#[serde(default = "too_many_requests_error_response")]
|
23
|
+
pub error_response: ErrorResponse,
|
24
|
+
}
|
25
|
+
|
26
|
+
fn too_many_requests_error_response() -> ErrorResponse {
|
27
|
+
ErrorResponse::too_many_requests()
|
28
|
+
}
|
29
|
+
|
30
|
+
#[derive(Debug, Clone, Deserialize)]
|
31
|
+
pub enum RateLimitKey {
|
32
|
+
#[serde(rename(deserialize = "address"))]
|
33
|
+
SocketAddress,
|
34
|
+
#[serde(rename(deserialize = "parameter"))]
|
35
|
+
Parameter(TokenSource),
|
36
|
+
}
|
37
|
+
|
38
|
+
#[async_trait]
|
39
|
+
impl MiddlewareLayer for RateLimit {
|
40
|
+
async fn initialize(&self) -> Result<()> {
|
41
|
+
// Instantiate our rate limiter based on the rate limit config here.
|
42
|
+
// This will automatically fall back to in-memory if Redis fails
|
43
|
+
if let Ok(limiter) = get_rate_limiter(&self.store_config).await {
|
44
|
+
let _ = self.rate_limiter.set(limiter);
|
45
|
+
}
|
46
|
+
Ok(())
|
47
|
+
}
|
48
|
+
|
49
|
+
async fn before(
|
50
|
+
&self,
|
51
|
+
req: HttpRequest,
|
52
|
+
context: &mut HttpRequestContext,
|
53
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
54
|
+
// Get the key to rate limit on
|
55
|
+
let key_value = match &self.key {
|
56
|
+
RateLimitKey::SocketAddress => {
|
57
|
+
// Use the socket address from the context
|
58
|
+
&context.addr
|
59
|
+
}
|
60
|
+
RateLimitKey::Parameter(token_source) => {
|
61
|
+
match token_source {
|
62
|
+
TokenSource::Header { name, prefix } => {
|
63
|
+
if let Some(header) = req.header(name) {
|
64
|
+
if let Some(prefix) = prefix {
|
65
|
+
header.strip_prefix(prefix).unwrap_or("").trim_ascii()
|
66
|
+
} else {
|
67
|
+
header.trim_ascii()
|
68
|
+
}
|
69
|
+
} else {
|
70
|
+
// If no token is found, skip rate limiting
|
71
|
+
tracing::warn!("No token found in header for rate limiting");
|
72
|
+
return Ok(Either::Left(req));
|
73
|
+
}
|
74
|
+
}
|
75
|
+
TokenSource::Query(query_name) => {
|
76
|
+
if let Some(value) = req.query_param(query_name) {
|
77
|
+
value
|
78
|
+
} else {
|
79
|
+
// If no token is found, skip rate limiting
|
80
|
+
tracing::warn!("No token found in query for rate limiting");
|
81
|
+
return Ok(Either::Left(req));
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
};
|
87
|
+
|
88
|
+
// Create a rate limit key
|
89
|
+
let rate_limit_key = create_rate_limit_key(key_value, req.uri().path());
|
90
|
+
|
91
|
+
// Get the rate limiter
|
92
|
+
if let Some(limiter) = self.rate_limiter.get() {
|
93
|
+
// Check if rate limit is exceeded
|
94
|
+
let timeout = Duration::from_secs(self.seconds);
|
95
|
+
let limit = self.requests;
|
96
|
+
|
97
|
+
match limiter.check_limit(&rate_limit_key, limit, timeout).await {
|
98
|
+
Ok(_) => Ok(Either::Left(req)),
|
99
|
+
Err(RateLimitError::RateLimitExceeded { limit, ttl, .. }) => {
|
100
|
+
let mut response = self
|
101
|
+
.error_response
|
102
|
+
.to_http_response(req.accept().into())
|
103
|
+
.await;
|
104
|
+
response
|
105
|
+
.headers_mut()
|
106
|
+
.insert("X-RateLimit-Limit", limit.to_string().parse().unwrap());
|
107
|
+
response
|
108
|
+
.headers_mut()
|
109
|
+
.insert("X-RateLimit-Remaining", "0".parse().unwrap());
|
110
|
+
response
|
111
|
+
.headers_mut()
|
112
|
+
.insert("X-RateLimit-Reset", ttl.to_string().parse().unwrap());
|
113
|
+
response
|
114
|
+
.headers_mut()
|
115
|
+
.insert("Retry-After", ttl.to_string().parse().unwrap());
|
116
|
+
Ok(Either::Right(response))
|
117
|
+
}
|
118
|
+
Err(e) => {
|
119
|
+
// Other error, log and allow request (fail open)
|
120
|
+
tracing::error!("Rate limiter error: {:?}", e);
|
121
|
+
Ok(Either::Left(req))
|
122
|
+
}
|
123
|
+
}
|
124
|
+
} else {
|
125
|
+
// If rate limiter is not initialized, allow request
|
126
|
+
tracing::warn!("Rate limiter not initialized");
|
127
|
+
Ok(Either::Left(req))
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
impl FromValue for RateLimit {}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
use crate::{
|
2
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
3
|
+
services::itsi_http_service::HttpRequestContext,
|
4
|
+
};
|
5
|
+
|
6
|
+
use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
|
7
|
+
|
8
|
+
use async_trait::async_trait;
|
9
|
+
use either::Either;
|
10
|
+
use http::{Response, StatusCode};
|
11
|
+
use http_body_util::{combinators::BoxBody, Empty};
|
12
|
+
use magnus::error::Result;
|
13
|
+
use serde::Deserialize;
|
14
|
+
|
15
|
+
/// A simple API key filter.
|
16
|
+
/// The API key can be given inside the header or a query string
|
17
|
+
/// Keys are validated against a list of allowed key values (Changing these requires a restart)
|
18
|
+
///
|
19
|
+
#[derive(Debug, Clone, Deserialize)]
|
20
|
+
pub struct Redirect {
|
21
|
+
pub to: StringRewrite,
|
22
|
+
#[serde(default)]
|
23
|
+
#[serde(rename(deserialize = "type"))]
|
24
|
+
pub redirect_type: RedirectType,
|
25
|
+
}
|
26
|
+
|
27
|
+
#[derive(Debug, Clone, Deserialize, Default)]
|
28
|
+
pub enum RedirectType {
|
29
|
+
#[serde(rename(deserialize = "permanent"))]
|
30
|
+
#[default]
|
31
|
+
Permanent,
|
32
|
+
#[serde(rename(deserialize = "temporary"))]
|
33
|
+
Temporary,
|
34
|
+
#[serde(rename(deserialize = "found"))]
|
35
|
+
Found,
|
36
|
+
#[serde(rename(deserialize = "moved_permanently"))]
|
37
|
+
MovedPermanently,
|
38
|
+
}
|
39
|
+
|
40
|
+
#[async_trait]
|
41
|
+
impl MiddlewareLayer for Redirect {
|
42
|
+
async fn before(
|
43
|
+
&self,
|
44
|
+
req: HttpRequest,
|
45
|
+
context: &mut HttpRequestContext,
|
46
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
47
|
+
Ok(Either::Right(self.redirect_response(&req, context)?))
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
impl Redirect {
|
52
|
+
pub fn redirect_response(
|
53
|
+
&self,
|
54
|
+
req: &HttpRequest,
|
55
|
+
context: &mut HttpRequestContext,
|
56
|
+
) -> Result<HttpResponse> {
|
57
|
+
let mut response = Response::new(BoxBody::new(Empty::new()));
|
58
|
+
*response.status_mut() = match self.redirect_type {
|
59
|
+
RedirectType::Permanent => StatusCode::PERMANENT_REDIRECT,
|
60
|
+
RedirectType::Temporary => StatusCode::TEMPORARY_REDIRECT,
|
61
|
+
RedirectType::MovedPermanently => StatusCode::MOVED_PERMANENTLY,
|
62
|
+
RedirectType::Found => StatusCode::FOUND,
|
63
|
+
};
|
64
|
+
response.headers_mut().append(
|
65
|
+
"Location",
|
66
|
+
self.to.rewrite_request(req, context).parse().map_err(|e| {
|
67
|
+
magnus::Error::new(
|
68
|
+
magnus::exception::standard_error(),
|
69
|
+
format!("Invalid Rewrite String: {:?}: {}", self.to, e),
|
70
|
+
)
|
71
|
+
})?,
|
72
|
+
);
|
73
|
+
Ok(response)
|
74
|
+
}
|
75
|
+
}
|
76
|
+
impl FromValue for Redirect {}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
use std::collections::HashMap;
|
2
|
+
|
3
|
+
use crate::{
|
4
|
+
server::http_message_types::{HttpRequest, HttpResponse},
|
5
|
+
services::itsi_http_service::HttpRequestContext,
|
6
|
+
};
|
7
|
+
|
8
|
+
use super::{FromValue, MiddlewareLayer};
|
9
|
+
use async_trait::async_trait;
|
10
|
+
use either::Either;
|
11
|
+
use http::HeaderName;
|
12
|
+
use magnus::error::Result;
|
13
|
+
use serde::Deserialize;
|
14
|
+
|
15
|
+
#[derive(Debug, Clone, Deserialize)]
|
16
|
+
pub struct RequestHeaders {
|
17
|
+
pub additions: HashMap<String, Vec<String>>,
|
18
|
+
pub removals: Vec<String>,
|
19
|
+
}
|
20
|
+
|
21
|
+
#[async_trait]
|
22
|
+
impl MiddlewareLayer for RequestHeaders {
|
23
|
+
async fn before(
|
24
|
+
&self,
|
25
|
+
mut req: HttpRequest,
|
26
|
+
_: &mut HttpRequestContext,
|
27
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
28
|
+
let headers = req.headers_mut();
|
29
|
+
for removal in &self.removals {
|
30
|
+
headers.remove(removal);
|
31
|
+
}
|
32
|
+
for (header_name, header_values) in &self.additions {
|
33
|
+
for header_value in header_values {
|
34
|
+
if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
|
35
|
+
if let Ok(parsed_header_value) = header_value.parse() {
|
36
|
+
headers.append(parsed_header_name, parsed_header_value);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
Ok(Either::Left(req))
|
42
|
+
}
|
43
|
+
}
|
44
|
+
impl FromValue for RequestHeaders {}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
use std::collections::HashMap;
|
2
|
+
|
3
|
+
use super::{FromValue, MiddlewareLayer};
|
4
|
+
use crate::{
|
5
|
+
server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
|
6
|
+
};
|
7
|
+
use async_trait::async_trait;
|
8
|
+
use http::HeaderName;
|
9
|
+
use serde::Deserialize;
|
10
|
+
|
11
|
+
#[derive(Debug, Clone, Deserialize)]
|
12
|
+
pub struct ResponseHeaders {
|
13
|
+
pub additions: HashMap<String, Vec<String>>,
|
14
|
+
pub removals: Vec<String>,
|
15
|
+
}
|
16
|
+
|
17
|
+
#[async_trait]
|
18
|
+
impl MiddlewareLayer for ResponseHeaders {
|
19
|
+
async fn after(&self, mut resp: HttpResponse, _: &mut HttpRequestContext) -> HttpResponse {
|
20
|
+
let headers = resp.headers_mut();
|
21
|
+
for removal in &self.removals {
|
22
|
+
headers.remove(removal);
|
23
|
+
}
|
24
|
+
for (header_name, header_values) in &self.additions {
|
25
|
+
for header_value in header_values {
|
26
|
+
if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
|
27
|
+
if let Ok(parsed_header_value) = header_value.parse() {
|
28
|
+
headers.append(parsed_header_name, parsed_header_value);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
resp
|
34
|
+
}
|
35
|
+
}
|
36
|
+
impl FromValue for ResponseHeaders {}
|