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};
24
25use crate::core::{
26 Filter, FilterType, ProxyRequest, ProxyResponse, ProxyError
27};
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct LoggingFilterConfig {
32 #[serde(default = "default_true")]
34 pub log_request_headers: bool,
35
36 #[serde(default = "default_false")]
38 pub log_request_body: bool,
39
40 #[serde(default = "default_true")]
42 pub log_response_headers: bool,
43
44 #[serde(default = "default_false")]
46 pub log_response_body: bool,
47
48 #[serde(default = "default_log_level")]
50 pub log_level: String,
51
52 #[serde(default = "default_max_body_size")]
54 pub max_body_size: usize,
55}
56
57fn default_true() -> bool {
58 true
59}
60
61fn default_false() -> bool {
62 false
63}
64
65fn default_log_level() -> String {
66 "trace".to_string()
67}
68
69fn default_max_body_size() -> usize {
70 1024 }
72
73impl Default for LoggingFilterConfig {
74 fn default() -> Self {
75 Self {
76 log_request_headers: true,
77 log_request_body: false,
78 log_response_headers: true,
79 log_response_body: false,
80 log_level: "trace".to_string(),
81 max_body_size: 1024,
82 }
83 }
84}
85
86#[derive(Debug)]
88pub struct LoggingFilter {
89 config: LoggingFilterConfig,
90}
91
92impl LoggingFilter {
93 pub fn new(config: LoggingFilterConfig) -> Self {
95 Self { config }
96 }
97
98 pub fn default() -> Self {
100 Self::new(LoggingFilterConfig::default())
101 }
102
103 fn get_log_level(&self) -> Level {
105 match self.config.log_level.to_lowercase().as_str() {
106 "error" => Level::Error,
107 "warn" => Level::Warn,
108 "info" => Level::Info,
109 "debug" => Level::Debug,
110 "trace" => Level::Trace,
111 _ => Level::Trace,
112 }
113 }
114
115 fn log(&self, message: &str) {
117 match self.get_log_level() {
118 Level::Error => error!("{}", message),
119 Level::Warn => warn!("{}", message),
120 Level::Info => info!("{}", message),
121 Level::Debug => debug!("{}", message),
122 Level::Trace => trace!("{}", message),
123 }
124 }
125
126 fn format_headers(&self, headers: &reqwest::header::HeaderMap) -> String {
128 let mut header_lines = Vec::new();
129 for (name, value) in headers.iter() {
130 if let Ok(value_str) = value.to_str() {
131 header_lines.push(format!("{}: {}", name, value_str));
132 }
133 }
134 header_lines.join("\n")
135 }
136
137 fn format_body(&self, body: &[u8]) -> String {
139 if body.is_empty() {
140 return "[Empty body]".to_string();
141 }
142
143 let body_size = body.len();
144
145 if body_size > self.config.max_body_size {
146 return format!(
147 "[Body truncated, showing {}/{} bytes]\n{}",
148 self.config.max_body_size,
149 body_size,
150 String::from_utf8_lossy(&body[0..self.config.max_body_size])
151 );
152 }
153
154 String::from_utf8_lossy(body).to_string()
155 }
156}
157
158#[async_trait]
159impl Filter for LoggingFilter {
160 fn filter_type(&self) -> FilterType {
161 FilterType::Both
162 }
163
164 fn name(&self) -> &str {
165 "logging"
166 }
167
168 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
169 if self.config.log_request_headers {
170 self.log(&format!(">> {} {}", request.method, request.path));
171 for (k, v) in request.headers.iter() {
172 self.log(&format!(">> {}: {:?}", k, v));
173 }
174 }
175 if self.config.log_request_body {
176 let (new_body, snippet) = tee_body(request.body, 1_000).await?;
177 let truncated = if snippet.len() == 1000 {"(truncated)"} else {""};
178
179 self.log(&format!(">> Request Body:\n{}{}", snippet, truncated));
180 request.body = new_body;
181 }
182 Ok(request)
183 }
184
185 async fn post_filter(
186 &self,
187 _req: ProxyRequest,
188 mut response: ProxyResponse,
189 ) -> Result<ProxyResponse, ProxyError> {
190 if self.config.log_response_headers {
191 self.log(&format!("<< {}", response.status));
192 for (k, v) in response.headers.iter() {
193 self.log(&format!("<< {}: {:?}", k, v));
194 }
195 }
196 if self.config.log_response_body {
197 let (new_body, snippet) = tee_body(response.body, 1_000).await?;
198 let truncated = if snippet.len() == 1000 {"(truncated)"} else {""};
199
200 self.log(&format!(">> Response Body:\n{}{}", snippet, truncated));
201 response.body = new_body;
202 }
203 Ok(response)
204 }
205}
206
207async fn tee_body(
208 body: reqwest::Body,
209 limit: usize,
210) -> Result<(reqwest::Body, String), ProxyError> {
211 let mut stream_in = body.into_data_stream();
213
214 let mut captured = Vec::<u8>::with_capacity(limit);
216
217 let mut chunks = Vec::new();
219
220 while captured.len() < limit {
222 match stream_in.next().await {
223 Some(Ok(chunk)) => {
224 let chunk_clone = chunk.clone();
226 chunks.push(Ok(chunk));
227
228 if captured.len() < limit {
230 let remaining = limit - captured.len();
231 let take = cmp::min(remaining, chunk_clone.len());
232 captured.extend_from_slice(&chunk_clone[..take]);
233 }
234 }
235 Some(Err(e)) => return Err(ProxyError::Other(e.to_string())),
236 None => break, }
238 }
239
240 let combined_stream = futures_util::stream::iter(chunks)
242 .chain(stream_in.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)));
243
244 let new_body = reqwest::Body::wrap_stream(combined_stream);
246
247 let snippet = String::from_utf8_lossy(&captured).to_string();
249
250 Ok((new_body, snippet))
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct HeaderFilterConfig {
256 #[serde(default)]
258 pub add_request_headers: std::collections::HashMap<String, String>,
259
260 #[serde(default)]
262 pub remove_request_headers: Vec<String>,
263
264 #[serde(default)]
266 pub add_response_headers: std::collections::HashMap<String, String>,
267
268 #[serde(default)]
270 pub remove_response_headers: Vec<String>,
271}
272
273impl Default for HeaderFilterConfig {
274 fn default() -> Self {
275 Self {
276 add_request_headers: std::collections::HashMap::new(),
277 remove_request_headers: Vec::new(),
278 add_response_headers: std::collections::HashMap::new(),
279 remove_response_headers: Vec::new(),
280 }
281 }
282}
283
284#[derive(Debug)]
286pub struct HeaderFilter {
287 config: HeaderFilterConfig,
288}
289
290impl HeaderFilter {
291 pub fn new(config: HeaderFilterConfig) -> Self {
293 Self { config }
294 }
295
296 pub fn default() -> Self {
298 Self::new(HeaderFilterConfig::default())
299 }
300
301 fn apply_headers(&self, headers: &mut reqwest::header::HeaderMap,
303 add_headers: &std::collections::HashMap<String, String>,
304 remove_headers: &[String]) {
305 for header_name in remove_headers {
307 if let Ok(name) = reqwest::header::HeaderName::from_bytes(header_name.as_bytes()) {
308 headers.remove(&name);
309 }
310 }
311
312 for (name, value) in add_headers {
314 if let (Ok(header_name), Ok(header_value)) = (
315 reqwest::header::HeaderName::from_bytes(name.as_bytes()),
316 reqwest::header::HeaderValue::from_str(value)
317 ) {
318 headers.insert(header_name, header_value);
319 }
320 }
321 }
322}
323
324#[async_trait]
325impl Filter for HeaderFilter {
326 fn filter_type(&self) -> FilterType {
327 FilterType::Both
328 }
329
330 fn name(&self) -> &str {
331 "header"
332 }
333
334 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
335 self.apply_headers(
336 &mut request.headers,
337 &self.config.add_request_headers,
338 &self.config.remove_request_headers
339 );
340
341 Ok(request)
342 }
343
344 async fn post_filter(&self, _request: ProxyRequest, mut response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
345 self.apply_headers(
346 &mut response.headers,
347 &self.config.add_response_headers,
348 &self.config.remove_response_headers
349 );
350
351 Ok(response)
352 }
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct TimeoutFilterConfig {
358 pub timeout_ms: u64,
360}
361
362impl Default for TimeoutFilterConfig {
363 fn default() -> Self {
364 Self {
365 timeout_ms: 30000, }
367 }
368}
369
370#[derive(Debug)]
372pub struct TimeoutFilter {
373 config: TimeoutFilterConfig,
374}
375
376impl TimeoutFilter {
377 pub fn new(config: TimeoutFilterConfig) -> Self {
379 Self { config }
380 }
381
382 pub fn default() -> Self {
384 Self::new(TimeoutFilterConfig::default())
385 }
386}
387
388#[async_trait]
389impl Filter for TimeoutFilter {
390 fn filter_type(&self) -> FilterType {
391 FilterType::Pre
392 }
393
394 fn name(&self) -> &str {
395 "timeout"
396 }
397
398 async fn pre_filter(&self, request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
399 match request.context.write().await {
401 mut context => {
402 context.attributes.insert(
403 "timeout_ms".to_string(),
404 serde_json::to_value(self.config.timeout_ms).unwrap()
405 );
406 }
407 }
408
409 Ok(request)
410 }
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct PathRewriteFilterConfig {
416 pub pattern: String,
418 pub replacement: String,
420 #[serde(default = "default_true")]
422 pub rewrite_request: bool,
423 #[serde(default = "default_false")]
425 pub rewrite_response: bool,
426}
427
428#[derive(Debug)]
430pub struct PathRewriteFilter {
431 config: PathRewriteFilterConfig,
433 regex: Regex,
435}
436
437impl PathRewriteFilter {
438 pub fn new(config: PathRewriteFilterConfig) -> Self {
440 let regex = Regex::new(&config.pattern)
442 .expect("Failed to compile path rewrite pattern");
443
444 Self { config, regex }
445 }
446
447 pub fn default() -> Self {
449 Self::new(PathRewriteFilterConfig {
450 pattern: "(.*)".to_string(),
451 replacement: "$1".to_string(),
452 rewrite_request: true,
453 rewrite_response: false,
454 })
455 }
456}
457
458#[async_trait]
459impl Filter for PathRewriteFilter {
460 fn filter_type(&self) -> FilterType {
461 FilterType::Both
462 }
463
464 fn name(&self) -> &str {
465 "path_rewrite"
466 }
467
468 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
469 if self.config.rewrite_request {
470 let rewritten_path = self.regex.replace_all(&request.path, &self.config.replacement).to_string();
472
473 if rewritten_path != request.path {
474 debug!("Rewriting path from {} to {}", request.path, rewritten_path);
475 request.path = rewritten_path;
476 }
477 }
478
479 Ok(request)
480 }
481
482 async fn post_filter(&self, _request: ProxyRequest, response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
483 Ok(response)
488 }
489}
490
491#[derive(Debug)]
493pub struct FilterFactory;
494
495impl FilterFactory {
496 pub fn create_filter(filter_type: &str, config: serde_json::Value) -> Result<Arc<dyn Filter>, ProxyError> {
498 match filter_type {
499 "logging" => {
500 let config: LoggingFilterConfig = serde_json::from_value(config)
501 .map_err(|e| ProxyError::FilterError(format!("Invalid logging filter config: {}", e)))?;
502 Ok(Arc::new(LoggingFilter::new(config)))
503 },
504 "header" => {
505 let config: HeaderFilterConfig = serde_json::from_value(config)
506 .map_err(|e| ProxyError::FilterError(format!("Invalid header filter config: {}", e)))?;
507 Ok(Arc::new(HeaderFilter::new(config)))
508 },
509 "timeout" => {
510 let config: TimeoutFilterConfig = serde_json::from_value(config)
511 .map_err(|e| ProxyError::FilterError(format!("Invalid timeout filter config: {}", e)))?;
512 Ok(Arc::new(TimeoutFilter::new(config)))
513 },
514 "path_rewrite" => {
515 let config: PathRewriteFilterConfig = serde_json::from_value(config)
516 .map_err(|e| ProxyError::FilterError(format!("Invalid path rewrite filter config: {}", e)))?;
517 Ok(Arc::new(PathRewriteFilter::new(config)))
518 },
519 _ => Err(ProxyError::FilterError(format!("Unknown filter type: {}", filter_type))),
520 }
521 }
522}