1#[cfg(test)]
12mod tests;
13
14use std::cmp;
15use std::sync::Arc;
16use std::time::Instant;
17use async_trait::async_trait;
18use bytes::Bytes;
19use futures_util::{stream, StreamExt, TryStreamExt};
20use http_body_util::BodyExt;
21use log::{trace, debug, info, warn, error, Level};
22use regex::Regex;
23use serde::{Serialize, Deserialize};
24use once_cell::sync::Lazy;
25use std::collections::HashMap;
26use std::sync::RwLock;
27
28use crate::core::{
29 Filter, FilterType, ProxyRequest, ProxyResponse, ProxyError
30};
31
32#[cfg(feature = "opentelemetry")]
33use crate::opentelemetry::OpenTelemetryFilterFactory;
34
35pub type FilterConstructor =
37fn(serde_json::Value) -> Result<Arc<dyn Filter>, ProxyError>;
38
39
40static FILTER_REGISTRY: Lazy<RwLock<HashMap<String, FilterConstructor>>> =
43 Lazy::new(|| RwLock::new(HashMap::new()));
44
45pub fn register_filter(name: &str, ctor: FilterConstructor) {
70 FILTER_REGISTRY
71 .write()
72 .expect("FILTER_REGISTRY poisoned")
73 .insert(name.to_string(), ctor);
74}
75
76fn get_registered_filter(name: &str) -> Option<FilterConstructor> {
78 FILTER_REGISTRY
79 .read()
80 .expect("FILTER_REGISTRY poisoned")
81 .get(name)
82 .copied()
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct LoggingFilterConfig {
88 #[serde(default = "default_true")]
90 pub log_request_headers: bool,
91
92 #[serde(default = "default_false")]
94 pub log_request_body: bool,
95
96 #[serde(default = "default_true")]
98 pub log_response_headers: bool,
99
100 #[serde(default = "default_false")]
102 pub log_response_body: bool,
103
104 #[serde(default = "default_log_level")]
106 pub log_level: String,
107
108 #[serde(default = "default_max_body_size")]
110 pub max_body_size: usize,
111}
112
113fn default_true() -> bool {
114 true
115}
116
117fn default_false() -> bool {
118 false
119}
120
121fn default_log_level() -> String {
122 "trace".to_string()
123}
124
125fn default_max_body_size() -> usize {
126 1024 }
128
129impl Default for LoggingFilterConfig {
130 fn default() -> Self {
131 Self {
132 log_request_headers: true,
133 log_request_body: false,
134 log_response_headers: true,
135 log_response_body: false,
136 log_level: "trace".to_string(),
137 max_body_size: 1024,
138 }
139 }
140}
141
142#[derive(Debug)]
144pub struct LoggingFilter {
145 config: LoggingFilterConfig,
146}
147
148impl LoggingFilter {
149 pub fn new(config: LoggingFilterConfig) -> Self {
151 Self { config }
152 }
153
154 pub fn default() -> Self {
156 Self::new(LoggingFilterConfig::default())
157 }
158
159 fn get_log_level(&self) -> Level {
161 match self.config.log_level.to_lowercase().as_str() {
162 "error" => Level::Error,
163 "warn" => Level::Warn,
164 "info" => Level::Info,
165 "debug" => Level::Debug,
166 "trace" => Level::Trace,
167 _ => Level::Trace,
168 }
169 }
170
171 fn log(&self, message: &str) {
173 match self.get_log_level() {
174 Level::Error => error!("{}", message),
175 Level::Warn => warn!("{}", message),
176 Level::Info => info!("{}", message),
177 Level::Debug => debug!("{}", message),
178 Level::Trace => trace!("{}", message),
179 }
180 }
181
182 fn format_headers(&self, headers: &reqwest::header::HeaderMap) -> String {
184 let mut header_lines = Vec::new();
185 for (name, value) in headers.iter() {
186 if let Ok(value_str) = value.to_str() {
187 header_lines.push(format!("{}: {}", name, value_str));
188 }
189 }
190 header_lines.join("\n")
191 }
192
193 fn format_body(&self, body: &[u8]) -> String {
195 if body.is_empty() {
196 return "[Empty body]".to_string();
197 }
198
199 let body_size = body.len();
200
201 if body_size > self.config.max_body_size {
202 return format!(
203 "[Body truncated, showing {}/{} bytes]\n{}",
204 self.config.max_body_size,
205 body_size,
206 String::from_utf8_lossy(&body[0..self.config.max_body_size])
207 );
208 }
209
210 String::from_utf8_lossy(body).to_string()
211 }
212}
213
214#[async_trait]
215impl Filter for LoggingFilter {
216 fn filter_type(&self) -> FilterType {
217 FilterType::Both
218 }
219
220 fn name(&self) -> &str {
221 "logging"
222 }
223
224 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
225 if self.config.log_request_headers {
226 self.log(&format!(">> {} {}", request.method, request.path));
227 for (k, v) in request.headers.iter() {
228 self.log(&format!(">> {}: {:?}", k, v));
229 }
230 }
231 if self.config.log_request_body {
232 let (new_body, snippet) = tee_body(request.body, 1_000).await?;
233 let truncated = if snippet.len() == 1000 {"(truncated)"} else {""};
234
235 self.log(&format!(">> Request Body:\n{}{}", snippet, truncated));
236 request.body = new_body;
237 }
238 Ok(request)
239 }
240
241 async fn post_filter(
242 &self,
243 _req: ProxyRequest,
244 mut response: ProxyResponse,
245 ) -> Result<ProxyResponse, ProxyError> {
246 if self.config.log_response_headers {
247 self.log(&format!("<< {}", response.status));
248 for (k, v) in response.headers.iter() {
249 self.log(&format!("<< {}: {:?}", k, v));
250 }
251 }
252 if self.config.log_response_body {
253 let (new_body, snippet) = tee_body(response.body, 1_000).await?;
254 let truncated = if snippet.len() == 1000 {"(truncated)"} else {""};
255
256 self.log(&format!(">> Response Body:\n{}{}", snippet, truncated));
257 response.body = new_body;
258 }
259 Ok(response)
260 }
261}
262
263async fn tee_body(
264 body: reqwest::Body,
265 limit: usize,
266) -> Result<(reqwest::Body, String), ProxyError> {
267 let mut stream_in = body.into_data_stream();
269
270 let mut captured = Vec::<u8>::with_capacity(limit);
272
273 let mut chunks = Vec::new();
275
276 while captured.len() < limit {
278 match stream_in.next().await {
279 Some(Ok(chunk)) => {
280 let chunk_clone = chunk.clone();
282 chunks.push(Ok(chunk));
283
284 if captured.len() < limit {
286 let remaining = limit - captured.len();
287 let take = cmp::min(remaining, chunk_clone.len());
288 captured.extend_from_slice(&chunk_clone[..take]);
289 }
290 }
291 Some(Err(e)) => return Err(ProxyError::Other(e.to_string())),
292 None => break, }
294 }
295
296 let combined_stream = futures_util::stream::iter(chunks)
298 .chain(stream_in.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)));
299
300 let new_body = reqwest::Body::wrap_stream(combined_stream);
302
303 let snippet = String::from_utf8_lossy(&captured).to_string();
305
306 Ok((new_body, snippet))
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct HeaderFilterConfig {
312 #[serde(default)]
314 pub add_request_headers: std::collections::HashMap<String, String>,
315
316 #[serde(default)]
318 pub remove_request_headers: Vec<String>,
319
320 #[serde(default)]
322 pub add_response_headers: std::collections::HashMap<String, String>,
323
324 #[serde(default)]
326 pub remove_response_headers: Vec<String>,
327}
328
329impl Default for HeaderFilterConfig {
330 fn default() -> Self {
331 Self {
332 add_request_headers: std::collections::HashMap::new(),
333 remove_request_headers: Vec::new(),
334 add_response_headers: std::collections::HashMap::new(),
335 remove_response_headers: Vec::new(),
336 }
337 }
338}
339
340#[derive(Debug)]
342pub struct HeaderFilter {
343 config: HeaderFilterConfig,
344}
345
346impl HeaderFilter {
347 pub fn new(config: HeaderFilterConfig) -> Self {
349 Self { config }
350 }
351
352 pub fn default() -> Self {
354 Self::new(HeaderFilterConfig::default())
355 }
356
357 fn apply_headers(&self, headers: &mut reqwest::header::HeaderMap,
359 add_headers: &std::collections::HashMap<String, String>,
360 remove_headers: &[String]) {
361 for header_name in remove_headers {
363 if let Ok(name) = reqwest::header::HeaderName::from_bytes(header_name.as_bytes()) {
364 headers.remove(&name);
365 }
366 }
367
368 for (name, value) in add_headers {
370 if let (Ok(header_name), Ok(header_value)) = (
371 reqwest::header::HeaderName::from_bytes(name.as_bytes()),
372 reqwest::header::HeaderValue::from_str(value)
373 ) {
374 headers.insert(header_name, header_value);
375 }
376 }
377 }
378}
379
380#[async_trait]
381impl Filter for HeaderFilter {
382 fn filter_type(&self) -> FilterType {
383 FilterType::Both
384 }
385
386 fn name(&self) -> &str {
387 "header"
388 }
389
390 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
391 self.apply_headers(
392 &mut request.headers,
393 &self.config.add_request_headers,
394 &self.config.remove_request_headers
395 );
396
397 Ok(request)
398 }
399
400 async fn post_filter(&self, _request: ProxyRequest, mut response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
401 self.apply_headers(
402 &mut response.headers,
403 &self.config.add_response_headers,
404 &self.config.remove_response_headers
405 );
406
407 Ok(response)
408 }
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct TimeoutFilterConfig {
414 pub timeout_ms: u64,
416}
417
418impl Default for TimeoutFilterConfig {
419 fn default() -> Self {
420 Self {
421 timeout_ms: 30000, }
423 }
424}
425
426#[derive(Debug)]
428pub struct TimeoutFilter {
429 config: TimeoutFilterConfig,
430}
431
432impl TimeoutFilter {
433 pub fn new(config: TimeoutFilterConfig) -> Self {
435 Self { config }
436 }
437
438 pub fn default() -> Self {
440 Self::new(TimeoutFilterConfig::default())
441 }
442}
443
444#[async_trait]
445impl Filter for TimeoutFilter {
446 fn filter_type(&self) -> FilterType {
447 FilterType::Pre
448 }
449
450 fn name(&self) -> &str {
451 "timeout"
452 }
453
454 async fn pre_filter(&self, request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
455 match request.context.write().await {
457 mut context => {
458 context.attributes.insert(
459 "timeout_ms".to_string(),
460 serde_json::to_value(self.config.timeout_ms).unwrap()
461 );
462 }
463 }
464
465 Ok(request)
466 }
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
471pub struct PathRewriteFilterConfig {
472 pub pattern: String,
474 pub replacement: String,
476 #[serde(default = "default_true")]
478 pub rewrite_request: bool,
479 #[serde(default = "default_false")]
481 pub rewrite_response: bool,
482}
483
484#[derive(Debug)]
486pub struct PathRewriteFilter {
487 config: PathRewriteFilterConfig,
489 regex: Regex,
491}
492
493impl PathRewriteFilter {
494 pub fn new(config: PathRewriteFilterConfig) -> Result<Self, ProxyError> {
496 let regex = Regex::new(&config.pattern)
498 .map_err(|e| {
499 let err = ProxyError::FilterError(format!("Invalid regex pattern '{}': {}", config.pattern, e));
500 log::error!("{}", err);
501 err
502 })?;
503
504 Ok(Self { config, regex })
505 }
506
507 pub fn default() -> Result<Self, ProxyError> {
509 Self::new(PathRewriteFilterConfig {
510 pattern: "(.*)".to_string(),
511 replacement: "$1".to_string(),
512 rewrite_request: true,
513 rewrite_response: false,
514 })
515 }
516}
517
518#[async_trait]
519impl Filter for PathRewriteFilter {
520 fn filter_type(&self) -> FilterType {
521 FilterType::Both
522 }
523
524 fn name(&self) -> &str {
525 "path_rewrite"
526 }
527
528 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
529 if self.config.rewrite_request {
530 let original_path = request.path.clone();
532 let rewritten_path = self.regex.replace_all(&request.path, &self.config.replacement).to_string();
533
534 if rewritten_path != original_path {
535 log::debug!("Rewriting path from {} to {}", original_path, rewritten_path);
536 request.path = rewritten_path;
537 } else {
538 log::trace!("Path rewrite pattern matched but did not change path: {}", original_path);
539 }
540 }
541
542 Ok(request)
543 }
544
545 async fn post_filter(&self, _request: ProxyRequest, response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
546 if self.config.rewrite_response {
547 log::debug!("Response path rewriting is configured but not implemented yet");
548 }
552
553 Ok(response)
554 }
555}
556
557#[derive(Debug)]
559pub struct FilterFactory;
560
561impl FilterFactory {
562 pub fn create_filter(filter_type: &str, config: serde_json::Value) -> Result<Arc<dyn Filter>, ProxyError> {
564 log::debug!("Creating filter of type '{}' with config: {}", filter_type, config);
565
566 if let Some(ctor) = get_registered_filter(filter_type) {
568 return ctor(config);
569 }
570
571 match filter_type {
572 "logging" => {
573 let config: LoggingFilterConfig = serde_json::from_value(config)
574 .map_err(|e| {
575 let err = ProxyError::FilterError(format!("Invalid logging filter config: {}", e));
576 log::error!("{}", err);
577 err
578 })?;
579 Ok(Arc::new(LoggingFilter::new(config)))
580 },
581 "header" => {
582 let config: HeaderFilterConfig = serde_json::from_value(config)
583 .map_err(|e| {
584 let err = ProxyError::FilterError(format!("Invalid header filter config: {}", e));
585 log::error!("{}", err);
586 err
587 })?;
588 Ok(Arc::new(HeaderFilter::new(config)))
589 },
590 "timeout" => {
591 let config: TimeoutFilterConfig = serde_json::from_value(config)
592 .map_err(|e| {
593 let err = ProxyError::FilterError(format!("Invalid timeout filter config: {}", e));
594 log::error!("{}", err);
595 err
596 })?;
597 Ok(Arc::new(TimeoutFilter::new(config)))
598 },
599 "path_rewrite" => {
600 let config: PathRewriteFilterConfig = serde_json::from_value(config)
601 .map_err(|e| {
602 let err = ProxyError::FilterError(format!("Invalid path rewrite filter config: {}", e));
603 log::error!("{}", err);
604 err
605 })?;
606
607 match PathRewriteFilter::new(config) {
608 Ok(filter) => Ok(Arc::new(filter)),
609 Err(e) => {
610 log::error!("Failed to create path rewrite filter: {}", e);
611 Err(e)
612 }
613 }
614 },
615 #[cfg(feature = "opentelemetry")]
616 "opentelemetry" => {
617 let config: crate::opentelemetry::OpenTelemetryConfig = serde_json::from_value(config)
618 .map_err(|e| {
619 let err = ProxyError::FilterError(format!("Invalid OpenTelemetry filter config: {}", e));
620 log::error!("{}", err);
621 err
622 })?;
623 Ok(Arc::new(crate::opentelemetry::OpenTelemetryFilter::new(config)))
624 },
625 _ => {
626 let err = ProxyError::FilterError(format!("Unknown filter type: {}", filter_type));
627 log::error!("{}", err);
628 Err(err)
629 },
630 }
631 }
632}