mockforge_core/proxy/
handler.rs

1//! Proxy request handler
2
3use super::client::ProxyClient;
4use crate::{Error, Result};
5use axum::http::{HeaderMap, Method, Uri};
6use std::collections::HashMap;
7
8/// HTTP proxy request handler
9pub struct ProxyHandler {
10    /// Handler configuration
11    pub config: super::config::ProxyConfig,
12}
13
14impl ProxyHandler {
15    /// Create a new proxy handler
16    pub fn new(config: super::config::ProxyConfig) -> Self {
17        Self { config }
18    }
19
20    /// Handle an HTTP request by proxying it
21    pub async fn handle_request(
22        &self,
23        method: &str,
24        url: &str,
25        headers: &HashMap<String, String>,
26        body: Option<&[u8]>,
27    ) -> Result<ProxyResponse> {
28        if !self.config.enabled {
29            return Err(Error::generic("Proxy is not enabled"));
30        }
31
32        // Parse method
33        let reqwest_method = match method.to_uppercase().as_str() {
34            "GET" => reqwest::Method::GET,
35            "POST" => reqwest::Method::POST,
36            "PUT" => reqwest::Method::PUT,
37            "DELETE" => reqwest::Method::DELETE,
38            "HEAD" => reqwest::Method::HEAD,
39            "OPTIONS" => reqwest::Method::OPTIONS,
40            "PATCH" => reqwest::Method::PATCH,
41            _ => return Err(Error::generic(format!("Unsupported HTTP method: {}", method))),
42        };
43
44        // Prepare headers (merge with config headers)
45        let mut request_headers = headers.clone();
46        for (key, value) in &self.config.headers {
47            request_headers.insert(key.clone(), value.clone());
48        }
49
50        // Create proxy client and send request
51        let client = ProxyClient::new();
52        let response = client.send_request(reqwest_method, url, &request_headers, body).await?;
53
54        // Convert response back to ProxyResponse
55        let mut response_headers = HeaderMap::new();
56        for (key, value) in response.headers() {
57            if let Ok(header_name) = axum::http::HeaderName::try_from(key.as_str()) {
58                response_headers.insert(header_name, value.clone());
59            }
60        }
61
62        let status_code = response.status().as_u16();
63        let body_bytes = response
64            .bytes()
65            .await
66            .map_err(|e| Error::generic(format!("Failed to read response body: {}", e)))?;
67
68        Ok(ProxyResponse {
69            status_code,
70            headers: response_headers,
71            body: Some(body_bytes.to_vec()),
72        })
73    }
74
75    /// Proxy a request with full HTTP types
76    pub async fn proxy_request(
77        &self,
78        method: &Method,
79        uri: &Uri,
80        headers: &HeaderMap,
81        body: Option<&[u8]>,
82    ) -> Result<ProxyResponse> {
83        if !self.config.enabled {
84            return Err(Error::generic("Proxy is not enabled"));
85        }
86
87        // Check if this request should be proxied
88        if !self.config.should_proxy(method, uri.path()) {
89            return Err(Error::generic("Request should not be proxied"));
90        }
91
92        // Determine the upstream URL
93        let upstream_url = self.config.get_upstream_url(uri.path());
94
95        // Convert headers from HeaderMap to HashMap
96        let mut header_map = HashMap::new();
97        for (key, value) in headers {
98            if let Ok(value_str) = value.to_str() {
99                header_map.insert(key.to_string(), value_str.to_string());
100            }
101        }
102
103        // Add any configured headers
104        for (key, value) in &self.config.headers {
105            header_map.insert(key.clone(), value.clone());
106        }
107
108        // Convert method to reqwest method
109        let reqwest_method = match *method {
110            Method::GET => reqwest::Method::GET,
111            Method::POST => reqwest::Method::POST,
112            Method::PUT => reqwest::Method::PUT,
113            Method::DELETE => reqwest::Method::DELETE,
114            Method::HEAD => reqwest::Method::HEAD,
115            Method::OPTIONS => reqwest::Method::OPTIONS,
116            Method::PATCH => reqwest::Method::PATCH,
117            _ => return Err(Error::generic(format!("Unsupported HTTP method: {}", method))),
118        };
119
120        // Create proxy client and send request
121        let client = ProxyClient::new();
122        let response =
123            client.send_request(reqwest_method, &upstream_url, &header_map, body).await?;
124
125        // Convert response back to ProxyResponse
126        let mut response_headers = HeaderMap::new();
127        for (key, value) in response.headers() {
128            if let Ok(header_name) = axum::http::HeaderName::try_from(key.as_str()) {
129                response_headers.insert(header_name, value.clone());
130            }
131        }
132
133        let status_code = response.status().as_u16();
134        let body_bytes = response
135            .bytes()
136            .await
137            .map_err(|e| Error::generic(format!("Failed to read response body: {}", e)))?;
138
139        Ok(ProxyResponse {
140            status_code,
141            headers: response_headers,
142            body: Some(body_bytes.to_vec()),
143        })
144    }
145}
146
147/// Response from a proxy request
148pub struct ProxyResponse {
149    /// HTTP status code
150    pub status_code: u16,
151    /// Response headers
152    pub headers: HeaderMap,
153    /// Response body
154    pub body: Option<Vec<u8>>,
155}