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
32pub type FilterConstructor =
34fn(serde_json::Value) -> Result<Arc<dyn Filter>, ProxyError>;
35
36
37static FILTER_REGISTRY: Lazy<RwLock<HashMap<String, FilterConstructor>>> =
40 Lazy::new(|| RwLock::new(HashMap::new()));
41
42pub fn register_filter(name: &str, ctor: FilterConstructor) {
67 FILTER_REGISTRY
68 .write()
69 .expect("FILTER_REGISTRY poisoned")
70 .insert(name.to_string(), ctor);
71}
72
73fn get_registered_filter(name: &str) -> Option<FilterConstructor> {
75 FILTER_REGISTRY
76 .read()
77 .expect("FILTER_REGISTRY poisoned")
78 .get(name)
79 .copied()
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct LoggingFilterConfig {
85 #[serde(default = "default_true")]
87 pub log_request_headers: bool,
88
89 #[serde(default = "default_false")]
91 pub log_request_body: bool,
92
93 #[serde(default = "default_true")]
95 pub log_response_headers: bool,
96
97 #[serde(default = "default_false")]
99 pub log_response_body: bool,
100
101 #[serde(default = "default_log_level")]
103 pub log_level: String,
104
105 #[serde(default = "default_max_body_size")]
107 pub max_body_size: usize,
108}
109
110fn default_true() -> bool {
111 true
112}
113
114fn default_false() -> bool {
115 false
116}
117
118fn default_log_level() -> String {
119 "trace".to_string()
120}
121
122fn default_max_body_size() -> usize {
123 1024 }
125
126impl Default for LoggingFilterConfig {
127 fn default() -> Self {
128 Self {
129 log_request_headers: true,
130 log_request_body: false,
131 log_response_headers: true,
132 log_response_body: false,
133 log_level: "trace".to_string(),
134 max_body_size: 1024,
135 }
136 }
137}
138
139#[derive(Debug)]
141pub struct LoggingFilter {
142 config: LoggingFilterConfig,
143}
144
145impl LoggingFilter {
146 pub fn new(config: LoggingFilterConfig) -> Self {
148 Self { config }
149 }
150
151 pub fn default() -> Self {
153 Self::new(LoggingFilterConfig::default())
154 }
155
156 fn get_log_level(&self) -> Level {
158 match self.config.log_level.to_lowercase().as_str() {
159 "error" => Level::Error,
160 "warn" => Level::Warn,
161 "info" => Level::Info,
162 "debug" => Level::Debug,
163 "trace" => Level::Trace,
164 _ => Level::Trace,
165 }
166 }
167
168 fn log(&self, message: &str) {
170 match self.get_log_level() {
171 Level::Error => error!("{}", message),
172 Level::Warn => warn!("{}", message),
173 Level::Info => info!("{}", message),
174 Level::Debug => debug!("{}", message),
175 Level::Trace => trace!("{}", message),
176 }
177 }
178
179 fn format_headers(&self, headers: &reqwest::header::HeaderMap) -> String {
181 let mut header_lines = Vec::new();
182 for (name, value) in headers.iter() {
183 if let Ok(value_str) = value.to_str() {
184 header_lines.push(format!("{}: {}", name, value_str));
185 }
186 }
187 header_lines.join("\n")
188 }
189
190 fn format_body(&self, body: &[u8]) -> String {
192 if body.is_empty() {
193 return "[Empty body]".to_string();
194 }
195
196 let body_size = body.len();
197
198 if body_size > self.config.max_body_size {
199 return format!(
200 "[Body truncated, showing {}/{} bytes]\n{}",
201 self.config.max_body_size,
202 body_size,
203 String::from_utf8_lossy(&body[0..self.config.max_body_size])
204 );
205 }
206
207 String::from_utf8_lossy(body).to_string()
208 }
209}
210
211#[async_trait]
212impl Filter for LoggingFilter {
213 fn filter_type(&self) -> FilterType {
214 FilterType::Both
215 }
216
217 fn name(&self) -> &str {
218 "logging"
219 }
220
221 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
222 if self.config.log_request_headers {
223 self.log(&format!(">> {} {}", request.method, request.path));
224 for (k, v) in request.headers.iter() {
225 self.log(&format!(">> {}: {:?}", k, v));
226 }
227 }
228 if self.config.log_request_body {
229 let (new_body, snippet) = tee_body(request.body, 1_000).await?;
230 let truncated = if snippet.len() == 1000 {"(truncated)"} else {""};
231
232 self.log(&format!(">> Request Body:\n{}{}", snippet, truncated));
233 request.body = new_body;
234 }
235 Ok(request)
236 }
237
238 async fn post_filter(
239 &self,
240 _req: ProxyRequest,
241 mut response: ProxyResponse,
242 ) -> Result<ProxyResponse, ProxyError> {
243 if self.config.log_response_headers {
244 self.log(&format!("<< {}", response.status));
245 for (k, v) in response.headers.iter() {
246 self.log(&format!("<< {}: {:?}", k, v));
247 }
248 }
249 if self.config.log_response_body {
250 let (new_body, snippet) = tee_body(response.body, 1_000).await?;
251 let truncated = if snippet.len() == 1000 {"(truncated)"} else {""};
252
253 self.log(&format!(">> Response Body:\n{}{}", snippet, truncated));
254 response.body = new_body;
255 }
256 Ok(response)
257 }
258}
259
260async fn tee_body(
261 body: reqwest::Body,
262 limit: usize,
263) -> Result<(reqwest::Body, String), ProxyError> {
264 let mut stream_in = body.into_data_stream();
266
267 let mut captured = Vec::<u8>::with_capacity(limit);
269
270 let mut chunks = Vec::new();
272
273 while captured.len() < limit {
275 match stream_in.next().await {
276 Some(Ok(chunk)) => {
277 let chunk_clone = chunk.clone();
279 chunks.push(Ok(chunk));
280
281 if captured.len() < limit {
283 let remaining = limit - captured.len();
284 let take = cmp::min(remaining, chunk_clone.len());
285 captured.extend_from_slice(&chunk_clone[..take]);
286 }
287 }
288 Some(Err(e)) => return Err(ProxyError::Other(e.to_string())),
289 None => break, }
291 }
292
293 let combined_stream = futures_util::stream::iter(chunks)
295 .chain(stream_in.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)));
296
297 let new_body = reqwest::Body::wrap_stream(combined_stream);
299
300 let snippet = String::from_utf8_lossy(&captured).to_string();
302
303 Ok((new_body, snippet))
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct HeaderFilterConfig {
309 #[serde(default)]
311 pub add_request_headers: std::collections::HashMap<String, String>,
312
313 #[serde(default)]
315 pub remove_request_headers: Vec<String>,
316
317 #[serde(default)]
319 pub add_response_headers: std::collections::HashMap<String, String>,
320
321 #[serde(default)]
323 pub remove_response_headers: Vec<String>,
324}
325
326impl Default for HeaderFilterConfig {
327 fn default() -> Self {
328 Self {
329 add_request_headers: std::collections::HashMap::new(),
330 remove_request_headers: Vec::new(),
331 add_response_headers: std::collections::HashMap::new(),
332 remove_response_headers: Vec::new(),
333 }
334 }
335}
336
337#[derive(Debug)]
339pub struct HeaderFilter {
340 config: HeaderFilterConfig,
341}
342
343impl HeaderFilter {
344 pub fn new(config: HeaderFilterConfig) -> Self {
346 Self { config }
347 }
348
349 pub fn default() -> Self {
351 Self::new(HeaderFilterConfig::default())
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 TimeoutFilter {
430 pub fn new(config: TimeoutFilterConfig) -> Self {
432 Self { config }
433 }
434
435 pub fn default() -> Self {
437 Self::new(TimeoutFilterConfig::default())
438 }
439}
440
441#[async_trait]
442impl Filter for TimeoutFilter {
443 fn filter_type(&self) -> FilterType {
444 FilterType::Pre
445 }
446
447 fn name(&self) -> &str {
448 "timeout"
449 }
450
451 async fn pre_filter(&self, request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
452 match request.context.write().await {
454 mut context => {
455 context.attributes.insert(
456 "timeout_ms".to_string(),
457 serde_json::to_value(self.config.timeout_ms).unwrap()
458 );
459 }
460 }
461
462 Ok(request)
463 }
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct PathRewriteFilterConfig {
469 pub pattern: String,
471 pub replacement: String,
473 #[serde(default = "default_true")]
475 pub rewrite_request: bool,
476 #[serde(default = "default_false")]
478 pub rewrite_response: bool,
479}
480
481#[derive(Debug)]
483pub struct PathRewriteFilter {
484 config: PathRewriteFilterConfig,
486 regex: Regex,
488}
489
490impl PathRewriteFilter {
491 pub fn new(config: PathRewriteFilterConfig) -> Result<Self, ProxyError> {
493 let regex = Regex::new(&config.pattern)
495 .map_err(|e| {
496 let err = ProxyError::FilterError(format!("Invalid regex pattern '{}': {}", config.pattern, e));
497 log::error!("{}", err);
498 err
499 })?;
500
501 Ok(Self { config, regex })
502 }
503
504 pub fn default() -> 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.regex.replace_all(&request.path, &self.config.replacement).to_string();
530
531 if rewritten_path != original_path {
532 log::debug!("Rewriting path from {} to {}", original_path, rewritten_path);
533 request.path = rewritten_path;
534 } else {
535 log::trace!("Path rewrite pattern matched but did not change path: {}", original_path);
536 }
537 }
538
539 Ok(request)
540 }
541
542 async fn post_filter(&self, _request: ProxyRequest, response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
543 if self.config.rewrite_response {
544 log::debug!("Response path rewriting is configured but not implemented yet");
545 }
549
550 Ok(response)
551 }
552}
553
554#[derive(Debug)]
556pub struct FilterFactory;
557
558impl FilterFactory {
559 pub fn create_filter(filter_type: &str, config: serde_json::Value) -> Result<Arc<dyn Filter>, ProxyError> {
561 log::debug!("Creating filter of type '{}' with config: {}", filter_type, config);
562
563 if let Some(ctor) = get_registered_filter(filter_type) {
565 return ctor(config);
566 }
567
568 match filter_type {
569 "logging" => {
570 let config: LoggingFilterConfig = serde_json::from_value(config)
571 .map_err(|e| {
572 let err = ProxyError::FilterError(format!("Invalid logging filter config: {}", e));
573 log::error!("{}", err);
574 err
575 })?;
576 Ok(Arc::new(LoggingFilter::new(config)))
577 },
578 "header" => {
579 let config: HeaderFilterConfig = serde_json::from_value(config)
580 .map_err(|e| {
581 let err = ProxyError::FilterError(format!("Invalid header filter config: {}", e));
582 log::error!("{}", err);
583 err
584 })?;
585 Ok(Arc::new(HeaderFilter::new(config)))
586 },
587 "timeout" => {
588 let config: TimeoutFilterConfig = serde_json::from_value(config)
589 .map_err(|e| {
590 let err = ProxyError::FilterError(format!("Invalid timeout filter config: {}", e));
591 log::error!("{}", err);
592 err
593 })?;
594 Ok(Arc::new(TimeoutFilter::new(config)))
595 },
596 "path_rewrite" => {
597 let config: PathRewriteFilterConfig = serde_json::from_value(config)
598 .map_err(|e| {
599 let err = ProxyError::FilterError(format!("Invalid path rewrite filter config: {}", e));
600 log::error!("{}", err);
601 err
602 })?;
603
604 match PathRewriteFilter::new(config) {
605 Ok(filter) => Ok(Arc::new(filter)),
606 Err(e) => {
607 log::error!("Failed to create path rewrite filter: {}", e);
608 Err(e)
609 }
610 }
611 },
612 _ => {
613 let err = ProxyError::FilterError(format!("Unknown filter type: {}", filter_type));
614 log::error!("{}", err);
615 Err(err)
616 },
617 }
618 }
619}