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::Level;
22use crate::{trace, debug, info, warn, error, error_fmt, warn_fmt, info_fmt, debug_fmt, trace_fmt};
23use regex::Regex;
24use serde::{Serialize, Deserialize};
25use once_cell::sync::Lazy;
26use std::collections::HashMap;
27use std::sync::RwLock;
28
29use crate::core::{
30 Filter, FilterType, ProxyRequest, ProxyResponse, ProxyError
31};
32
33pub type FilterConstructor =
35fn(serde_json::Value) -> Result<Arc<dyn Filter>, ProxyError>;
36
37
38static FILTER_REGISTRY: Lazy<RwLock<HashMap<String, FilterConstructor>>> =
41 Lazy::new(|| RwLock::new(HashMap::new()));
42
43pub fn register_filter(name: &str, ctor: FilterConstructor) {
68 FILTER_REGISTRY
69 .write()
70 .expect("FILTER_REGISTRY poisoned")
71 .insert(name.to_string(), ctor);
72}
73
74fn get_registered_filter(name: &str) -> Option<FilterConstructor> {
76 FILTER_REGISTRY
77 .read()
78 .expect("FILTER_REGISTRY poisoned")
79 .get(name)
80 .copied()
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct LoggingFilterConfig {
86 #[serde(default = "default_true")]
88 pub log_request_headers: bool,
89
90 #[serde(default = "default_false")]
92 pub log_request_body: bool,
93
94 #[serde(default = "default_true")]
96 pub log_response_headers: bool,
97
98 #[serde(default = "default_false")]
100 pub log_response_body: bool,
101
102 #[serde(default = "default_log_level")]
104 pub log_level: String,
105
106 #[serde(default = "default_max_body_size")]
108 pub max_body_size: usize,
109}
110
111fn default_true() -> bool {
112 true
113}
114
115fn default_false() -> bool {
116 false
117}
118
119fn default_log_level() -> String {
120 "trace".to_string()
121}
122
123fn default_max_body_size() -> usize {
124 1024 }
126
127impl Default for LoggingFilterConfig {
128 fn default() -> Self {
129 Self {
130 log_request_headers: true,
131 log_request_body: false,
132 log_response_headers: true,
133 log_response_body: false,
134 log_level: "trace".to_string(),
135 max_body_size: 1024,
136 }
137 }
138}
139
140#[derive(Debug)]
142pub struct LoggingFilter {
143 config: LoggingFilterConfig,
144}
145
146impl LoggingFilter {
147 pub fn new(config: LoggingFilterConfig) -> Self {
149 Self { config }
150 }
151
152 pub fn default() -> Self {
154 Self::new(LoggingFilterConfig::default())
155 }
156
157 fn get_log_level(&self) -> Level {
159 match self.config.log_level.to_lowercase().as_str() {
160 "error" => Level::Error,
161 "warn" => Level::Warn,
162 "info" => Level::Info,
163 "debug" => Level::Debug,
164 "trace" => Level::Trace,
165 _ => Level::Trace,
166 }
167 }
168
169 fn log(&self, message: &str) {
171 match self.get_log_level() {
172 Level::Error => error_fmt!("LoggingFilter", "{}", message),
173 Level::Warn => warn_fmt!("LoggingFilter", "{}", message),
174 Level::Info => info_fmt!("LoggingFilter", "{}", message),
175 Level::Debug => debug_fmt!("LoggingFilter", "{}", message),
176 Level::Trace => trace_fmt!("LoggingFilter", "{}", message),
177 }
178 }
179
180 fn format_headers(&self, headers: &reqwest::header::HeaderMap) -> String {
182 let mut header_lines = Vec::new();
183 for (name, value) in headers.iter() {
184 if let Ok(value_str) = value.to_str() {
185 header_lines.push(format!("{}: {}", name, value_str));
186 }
187 }
188 header_lines.join("\n")
189 }
190
191 fn format_body(&self, body: &[u8]) -> String {
193 if body.is_empty() {
194 return "[Empty body]".to_string();
195 }
196
197 let body_size = body.len();
198
199 if body_size > self.config.max_body_size {
200 return format!(
201 "[Body truncated, showing {}/{} bytes]\n{}",
202 self.config.max_body_size,
203 body_size,
204 String::from_utf8_lossy(&body[0..self.config.max_body_size])
205 );
206 }
207
208 String::from_utf8_lossy(body).to_string()
209 }
210}
211
212#[async_trait]
213impl Filter for LoggingFilter {
214 fn filter_type(&self) -> FilterType {
215 FilterType::Both
216 }
217
218 fn name(&self) -> &str {
219 "logging"
220 }
221
222 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
223 if self.config.log_request_headers {
224 self.log(&format!(">> {} {}", request.method, request.path));
225 for (k, v) in request.headers.iter() {
226 self.log(&format!(">> {}: {:?}", k, v));
227 }
228 }
229 if self.config.log_request_body {
230 let (new_body, snippet) = tee_body(request.body, 1_000).await?;
231 let truncated = if snippet.len() == 1000 {"(truncated)"} else {""};
232
233 self.log(&format!(">> Request Body:\n{}{}", snippet, truncated));
234 request.body = new_body;
235 }
236 Ok(request)
237 }
238
239 async fn post_filter(
240 &self,
241 _req: ProxyRequest,
242 mut response: ProxyResponse,
243 ) -> Result<ProxyResponse, ProxyError> {
244 if self.config.log_response_headers {
245 self.log(&format!("<< {}", response.status));
246 for (k, v) in response.headers.iter() {
247 self.log(&format!("<< {}: {:?}", k, v));
248 }
249 }
250 if self.config.log_response_body {
251 let (new_body, snippet) = tee_body(response.body, 1_000).await?;
252 let truncated = if snippet.len() == 1000 {"(truncated)"} else {""};
253
254 self.log(&format!(">> Response Body:\n{}{}", snippet, truncated));
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 = futures_util::stream::iter(chunks)
296 .chain(stream_in.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)));
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)]
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
327impl Default for HeaderFilterConfig {
328 fn default() -> Self {
329 Self {
330 add_request_headers: std::collections::HashMap::new(),
331 remove_request_headers: Vec::new(),
332 add_response_headers: std::collections::HashMap::new(),
333 remove_response_headers: Vec::new(),
334 }
335 }
336}
337
338#[derive(Debug)]
340pub struct HeaderFilter {
341 config: HeaderFilterConfig,
342}
343
344impl HeaderFilter {
345 pub fn new(config: HeaderFilterConfig) -> Self {
347 Self { config }
348 }
349
350 pub fn default() -> Self {
352 Self::new(HeaderFilterConfig::default())
353 }
354
355 fn apply_headers(&self, headers: &mut reqwest::header::HeaderMap,
357 add_headers: &std::collections::HashMap<String, String>,
358 remove_headers: &[String]) {
359 for header_name in remove_headers {
361 if let Ok(name) = reqwest::header::HeaderName::from_bytes(header_name.as_bytes()) {
362 headers.remove(&name);
363 }
364 }
365
366 for (name, value) in add_headers {
368 if let (Ok(header_name), Ok(header_value)) = (
369 reqwest::header::HeaderName::from_bytes(name.as_bytes()),
370 reqwest::header::HeaderValue::from_str(value)
371 ) {
372 headers.insert(header_name, header_value);
373 }
374 }
375 }
376}
377
378#[async_trait]
379impl Filter for HeaderFilter {
380 fn filter_type(&self) -> FilterType {
381 FilterType::Both
382 }
383
384 fn name(&self) -> &str {
385 "header"
386 }
387
388 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
389 self.apply_headers(
390 &mut request.headers,
391 &self.config.add_request_headers,
392 &self.config.remove_request_headers
393 );
394
395 Ok(request)
396 }
397
398 async fn post_filter(&self, _request: ProxyRequest, mut response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
399 self.apply_headers(
400 &mut response.headers,
401 &self.config.add_response_headers,
402 &self.config.remove_response_headers
403 );
404
405 Ok(response)
406 }
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct TimeoutFilterConfig {
412 pub timeout_ms: u64,
414}
415
416impl Default for TimeoutFilterConfig {
417 fn default() -> Self {
418 Self {
419 timeout_ms: 30000, }
421 }
422}
423
424#[derive(Debug)]
426pub struct TimeoutFilter {
427 config: TimeoutFilterConfig,
428}
429
430impl TimeoutFilter {
431 pub fn new(config: TimeoutFilterConfig) -> Self {
433 Self { config }
434 }
435
436 pub fn default() -> Self {
438 Self::new(TimeoutFilterConfig::default())
439 }
440}
441
442#[async_trait]
443impl Filter for TimeoutFilter {
444 fn filter_type(&self) -> FilterType {
445 FilterType::Pre
446 }
447
448 fn name(&self) -> &str {
449 "timeout"
450 }
451
452 async fn pre_filter(&self, request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
453 match request.context.write().await {
455 mut context => {
456 context.attributes.insert(
457 "timeout_ms".to_string(),
458 serde_json::to_value(self.config.timeout_ms).unwrap()
459 );
460 }
461 }
462
463 Ok(request)
464 }
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct PathRewriteFilterConfig {
470 pub pattern: String,
472 pub replacement: String,
474 #[serde(default = "default_true")]
476 pub rewrite_request: bool,
477 #[serde(default = "default_false")]
479 pub rewrite_response: bool,
480}
481
482#[derive(Debug)]
484pub struct PathRewriteFilter {
485 config: PathRewriteFilterConfig,
487 regex: Regex,
489}
490
491impl PathRewriteFilter {
492 pub fn new(config: PathRewriteFilterConfig) -> Result<Self, ProxyError> {
494 let regex = Regex::new(&config.pattern)
496 .map_err(|e| {
497 let err = ProxyError::FilterError(format!("Invalid regex pattern '{}': {}", config.pattern, e));
498 error_fmt!("PathRewriteFilter", "{}", err);
499 err
500 })?;
501
502 Ok(Self { config, regex })
503 }
504
505 pub fn default() -> Result<Self, ProxyError> {
507 Self::new(PathRewriteFilterConfig {
508 pattern: "(.*)".to_string(),
509 replacement: "$1".to_string(),
510 rewrite_request: true,
511 rewrite_response: false,
512 })
513 }
514}
515
516#[async_trait]
517impl Filter for PathRewriteFilter {
518 fn filter_type(&self) -> FilterType {
519 FilterType::Both
520 }
521
522 fn name(&self) -> &str {
523 "path_rewrite"
524 }
525
526 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
527 if self.config.rewrite_request {
528 let original_path = request.path.clone();
530 let rewritten_path = self.regex.replace_all(&request.path, &self.config.replacement).to_string();
531
532 if rewritten_path != original_path {
533 debug_fmt!("PathRewriteFilter", "Rewriting path from {} to {}", original_path, rewritten_path);
534 request.path = rewritten_path;
535 } else {
536 trace_fmt!("PathRewriteFilter", "Path rewrite pattern matched but did not change path: {}", original_path);
537 }
538 }
539
540 Ok(request)
541 }
542
543 async fn post_filter(&self, _request: ProxyRequest, response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
544 if self.config.rewrite_response {
545 debug_fmt!("PathRewriteFilter", "Response path rewriting is configured but not implemented yet");
546 }
550
551 Ok(response)
552 }
553}
554
555#[derive(Debug)]
557pub struct FilterFactory;
558
559impl FilterFactory {
560 pub fn create_filter(filter_type: &str, config: serde_json::Value) -> Result<Arc<dyn Filter>, ProxyError> {
562 debug_fmt!("Filter", "Creating filter of type '{}' with config: {}", filter_type, config);
563
564 if let Some(ctor) = get_registered_filter(filter_type) {
566 return ctor(config);
567 }
568
569 match filter_type {
570 "logging" => {
571 let config: LoggingFilterConfig = serde_json::from_value(config)
572 .map_err(|e| {
573 let err = ProxyError::FilterError(format!("Invalid logging filter config: {}", e));
574 error_fmt!("Filter", "{}", err);
575 err
576 })?;
577 Ok(Arc::new(LoggingFilter::new(config)))
578 },
579 "header" => {
580 let config: HeaderFilterConfig = serde_json::from_value(config)
581 .map_err(|e| {
582 let err = ProxyError::FilterError(format!("Invalid header filter config: {}", e));
583 error_fmt!("Filter", "{}", err);
584 err
585 })?;
586 Ok(Arc::new(HeaderFilter::new(config)))
587 },
588 "timeout" => {
589 let config: TimeoutFilterConfig = serde_json::from_value(config)
590 .map_err(|e| {
591 let err = ProxyError::FilterError(format!("Invalid timeout filter config: {}", e));
592 error_fmt!("Filter", "{}", err);
593 err
594 })?;
595 Ok(Arc::new(TimeoutFilter::new(config)))
596 },
597 "path_rewrite" => {
598 let config: PathRewriteFilterConfig = serde_json::from_value(config)
599 .map_err(|e| {
600 let err = ProxyError::FilterError(format!("Invalid path rewrite filter config: {}", e));
601 error_fmt!("Filter", "{}", err);
602 err
603 })?;
604
605 match PathRewriteFilter::new(config) {
606 Ok(filter) => Ok(Arc::new(filter)),
607 Err(e) => {
608 error_fmt!("Filter", "Failed to create path rewrite filter: {}", e);
609 Err(e)
610 }
611 }
612 },
613 _ => {
614 let err = ProxyError::FilterError(format!("Unknown filter type: {}", filter_type));
615 error_fmt!("Filter", "{}", err);
616 Err(err)
617 },
618 }
619 }
620}