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