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