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>::new();
216 let mut buffered = Vec::<Result<Bytes, std::io::Error>>::new();
217
218 while captured.len() < limit {
219 match stream_in.try_next().await {
220 Ok(Some(chunk)) => {
221 let take = cmp::min(limit - captured.len(), chunk.len());
222 captured.extend_from_slice(&chunk[..take]);
223 buffered.push(Ok(chunk));
224 }
225 Ok(None) => break, Err(e) => return Err(ProxyError::Other(e.to_string())),
227 }
228 }
229
230 let combined_stream = stream::iter(buffered).chain(
233 stream_in.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)),
234 );
235
236 let new_body = reqwest::Body::wrap_stream(combined_stream);
237 let snippet = String::from_utf8_lossy(&captured).to_string();
238
239 Ok((new_body, snippet))
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct HeaderFilterConfig {
245 #[serde(default)]
247 pub add_request_headers: std::collections::HashMap<String, String>,
248
249 #[serde(default)]
251 pub remove_request_headers: Vec<String>,
252
253 #[serde(default)]
255 pub add_response_headers: std::collections::HashMap<String, String>,
256
257 #[serde(default)]
259 pub remove_response_headers: Vec<String>,
260}
261
262impl Default for HeaderFilterConfig {
263 fn default() -> Self {
264 Self {
265 add_request_headers: std::collections::HashMap::new(),
266 remove_request_headers: Vec::new(),
267 add_response_headers: std::collections::HashMap::new(),
268 remove_response_headers: Vec::new(),
269 }
270 }
271}
272
273#[derive(Debug)]
275pub struct HeaderFilter {
276 config: HeaderFilterConfig,
277}
278
279impl HeaderFilter {
280 pub fn new(config: HeaderFilterConfig) -> Self {
282 Self { config }
283 }
284
285 pub fn default() -> Self {
287 Self::new(HeaderFilterConfig::default())
288 }
289
290 fn apply_headers(&self, headers: &mut reqwest::header::HeaderMap,
292 add_headers: &std::collections::HashMap<String, String>,
293 remove_headers: &[String]) {
294 for header_name in remove_headers {
296 if let Ok(name) = reqwest::header::HeaderName::from_bytes(header_name.as_bytes()) {
297 headers.remove(&name);
298 }
299 }
300
301 for (name, value) in add_headers {
303 if let (Ok(header_name), Ok(header_value)) = (
304 reqwest::header::HeaderName::from_bytes(name.as_bytes()),
305 reqwest::header::HeaderValue::from_str(value)
306 ) {
307 headers.insert(header_name, header_value);
308 }
309 }
310 }
311}
312
313#[async_trait]
314impl Filter for HeaderFilter {
315 fn filter_type(&self) -> FilterType {
316 FilterType::Both
317 }
318
319 fn name(&self) -> &str {
320 "header"
321 }
322
323 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
324 self.apply_headers(
325 &mut request.headers,
326 &self.config.add_request_headers,
327 &self.config.remove_request_headers
328 );
329
330 Ok(request)
331 }
332
333 async fn post_filter(&self, _request: ProxyRequest, mut response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
334 self.apply_headers(
335 &mut response.headers,
336 &self.config.add_response_headers,
337 &self.config.remove_response_headers
338 );
339
340 Ok(response)
341 }
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct TimeoutFilterConfig {
347 pub timeout_ms: u64,
349}
350
351impl Default for TimeoutFilterConfig {
352 fn default() -> Self {
353 Self {
354 timeout_ms: 30000, }
356 }
357}
358
359#[derive(Debug)]
361pub struct TimeoutFilter {
362 config: TimeoutFilterConfig,
363}
364
365impl TimeoutFilter {
366 pub fn new(config: TimeoutFilterConfig) -> Self {
368 Self { config }
369 }
370
371 pub fn default() -> Self {
373 Self::new(TimeoutFilterConfig::default())
374 }
375}
376
377#[async_trait]
378impl Filter for TimeoutFilter {
379 fn filter_type(&self) -> FilterType {
380 FilterType::Pre
381 }
382
383 fn name(&self) -> &str {
384 "timeout"
385 }
386
387 async fn pre_filter(&self, request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
388 match request.context.write().await {
390 mut context => {
391 context.attributes.insert(
392 "timeout_ms".to_string(),
393 serde_json::to_value(self.config.timeout_ms).unwrap()
394 );
395 }
396 }
397
398 Ok(request)
399 }
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct PathRewriteFilterConfig {
405 pub pattern: String,
407 pub replacement: String,
409 #[serde(default = "default_true")]
411 pub rewrite_request: bool,
412 #[serde(default = "default_false")]
414 pub rewrite_response: bool,
415}
416
417#[derive(Debug)]
419pub struct PathRewriteFilter {
420 config: PathRewriteFilterConfig,
422 regex: Regex,
424}
425
426impl PathRewriteFilter {
427 pub fn new(config: PathRewriteFilterConfig) -> Self {
429 let regex = Regex::new(&config.pattern)
431 .expect("Failed to compile path rewrite pattern");
432
433 Self { config, regex }
434 }
435
436 pub fn default() -> Self {
438 Self::new(PathRewriteFilterConfig {
439 pattern: "(.*)".to_string(),
440 replacement: "$1".to_string(),
441 rewrite_request: true,
442 rewrite_response: false,
443 })
444 }
445}
446
447#[async_trait]
448impl Filter for PathRewriteFilter {
449 fn filter_type(&self) -> FilterType {
450 FilterType::Both
451 }
452
453 fn name(&self) -> &str {
454 "path_rewrite"
455 }
456
457 async fn pre_filter(&self, mut request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
458 if self.config.rewrite_request {
459 let rewritten_path = self.regex.replace_all(&request.path, &self.config.replacement).to_string();
461
462 if rewritten_path != request.path {
463 debug!("Rewriting path from {} to {}", request.path, rewritten_path);
464 request.path = rewritten_path;
465 }
466 }
467
468 Ok(request)
469 }
470
471 async fn post_filter(&self, _request: ProxyRequest, response: ProxyResponse) -> Result<ProxyResponse, ProxyError> {
472 Ok(response)
477 }
478}
479
480#[derive(Debug)]
482pub struct FilterFactory;
483
484impl FilterFactory {
485 pub fn create_filter(filter_type: &str, config: serde_json::Value) -> Result<Arc<dyn Filter>, ProxyError> {
487 match filter_type {
488 "logging" => {
489 let config: LoggingFilterConfig = serde_json::from_value(config)
490 .map_err(|e| ProxyError::FilterError(format!("Invalid logging filter config: {}", e)))?;
491 Ok(Arc::new(LoggingFilter::new(config)))
492 },
493 "header" => {
494 let config: HeaderFilterConfig = serde_json::from_value(config)
495 .map_err(|e| ProxyError::FilterError(format!("Invalid header filter config: {}", e)))?;
496 Ok(Arc::new(HeaderFilter::new(config)))
497 },
498 "timeout" => {
499 let config: TimeoutFilterConfig = serde_json::from_value(config)
500 .map_err(|e| ProxyError::FilterError(format!("Invalid timeout filter config: {}", e)))?;
501 Ok(Arc::new(TimeoutFilter::new(config)))
502 },
503 "path_rewrite" => {
504 let config: PathRewriteFilterConfig = serde_json::from_value(config)
505 .map_err(|e| ProxyError::FilterError(format!("Invalid path rewrite filter config: {}", e)))?;
506 Ok(Arc::new(PathRewriteFilter::new(config)))
507 },
508 _ => Err(ProxyError::FilterError(format!("Unknown filter type: {}", filter_type))),
509 }
510 }
511}