Skip to main content

kindly_guard_server/transport/
http.rs

1// Copyright 2025 Kindly Software Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! HTTP transport implementation
15
16use anyhow::Result;
17use async_trait::async_trait;
18use axum::{extract::State, routing::post, Json, Router};
19use std::net::SocketAddr;
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::sync::Arc;
22use tokio::sync::{mpsc, Mutex};
23use tower::ServiceBuilder;
24use tower_http::cors::CorsLayer;
25use tracing::{debug, error, info};
26
27use super::{
28    ConnectionInfo, ConnectionStats, Transport, TransportConnection, TransportMessage,
29    TransportStats, TransportType,
30};
31
32use super::Deserialize;
33/// HTTP transport configuration
34use super::Serialize;
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct HttpConfig {
38    /// Bind address
39    pub bind_addr: String,
40    /// Enable TLS
41    pub tls: bool,
42    /// TLS certificate path
43    pub cert_path: Option<String>,
44    /// TLS key path
45    pub key_path: Option<String>,
46    /// Maximum request body size
47    pub max_body_size: usize,
48    /// Request timeout (ms)
49    pub request_timeout_ms: u64,
50}
51
52impl Default for HttpConfig {
53    fn default() -> Self {
54        Self {
55            bind_addr: "127.0.0.1:8080".to_string(),
56            tls: false,
57            cert_path: None,
58            key_path: None,
59            max_body_size: 10 * 1024 * 1024, // 10MB
60            request_timeout_ms: 30000,
61        }
62    }
63}
64
65/// HTTP transport for REST-style communication
66pub struct HttpTransport {
67    config: HttpConfig,
68    running: AtomicBool,
69    stats: Arc<Mutex<TransportStats>>,
70    shutdown_tx: Option<mpsc::Sender<()>>,
71}
72
73impl HttpTransport {
74    /// Create new HTTP transport
75    pub fn new(config: serde_json::Value) -> Result<Self> {
76        let config: HttpConfig = serde_json::from_value(config)?;
77
78        Ok(Self {
79            config,
80            running: AtomicBool::new(false),
81            stats: Arc::new(Mutex::new(TransportStats::default())),
82            shutdown_tx: None,
83        })
84    }
85
86    /// Start HTTP server
87    async fn start_server(&mut self) -> Result<mpsc::Sender<()>> {
88        let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
89        let addr: SocketAddr = self.config.bind_addr.parse()?;
90        let stats = self.stats.clone();
91
92        // Create router with basic endpoint
93        let app = Router::new()
94            .route("/rpc", post(handle_rpc))
95            .layer(
96                ServiceBuilder::new()
97                    .layer(CorsLayer::permissive())
98                    .into_inner(),
99            )
100            .with_state(stats);
101
102        // Start server
103        let listener = tokio::net::TcpListener::bind(&addr).await?;
104        info!("HTTP server listening on {}", addr);
105
106        tokio::spawn(async move {
107            axum::serve(listener, app)
108                .with_graceful_shutdown(async move {
109                    let _ = shutdown_rx.recv().await;
110                    info!("HTTP server shutting down");
111                })
112                .await
113                .map_err(|e| error!("Server error: {}", e))
114                .ok();
115        });
116
117        Ok(shutdown_tx)
118    }
119}
120
121#[async_trait]
122impl Transport for HttpTransport {
123    fn transport_type(&self) -> TransportType {
124        TransportType::Http
125    }
126
127    async fn start(&mut self) -> Result<()> {
128        if self.running.load(Ordering::Relaxed) {
129            return Err(anyhow::anyhow!("Transport already running"));
130        }
131
132        let shutdown_tx = self.start_server().await?;
133        self.shutdown_tx = Some(shutdown_tx);
134        self.running.store(true, Ordering::Relaxed);
135
136        info!("Started HTTP transport on {}", self.config.bind_addr);
137        Ok(())
138    }
139
140    async fn stop(&mut self) -> Result<()> {
141        if let Some(shutdown_tx) = self.shutdown_tx.take() {
142            let _ = shutdown_tx.send(()).await;
143        }
144
145        self.running.store(false, Ordering::Relaxed);
146        info!("Stopped HTTP transport");
147        Ok(())
148    }
149
150    async fn accept(&mut self) -> Result<Box<dyn TransportConnection>> {
151        if !self.running.load(Ordering::Relaxed) {
152            return Err(anyhow::anyhow!("Transport not running"));
153        }
154
155        // In real implementation, this would accept HTTP connections
156        // For now, return a stub connection
157        let mut stats = self.stats.lock().await;
158        stats.connections_accepted += 1;
159        stats.active_connections += 1;
160        drop(stats);
161
162        Ok(Box::new(HttpConnection::new(
163            "127.0.0.1:12345".to_string(),
164            self.stats.clone(),
165        )))
166    }
167
168    async fn connect(&mut self, address: &str) -> Result<Box<dyn TransportConnection>> {
169        // Create HTTP client connection
170        Ok(Box::new(HttpConnection::new(
171            address.to_string(),
172            self.stats.clone(),
173        )))
174    }
175
176    fn is_running(&self) -> bool {
177        self.running.load(Ordering::Relaxed)
178    }
179
180    fn get_stats(&self) -> TransportStats {
181        if let Ok(stats) = self.stats.try_lock() {
182            stats.clone()
183        } else {
184            TransportStats::default()
185        }
186    }
187
188    async fn set_option(&mut self, key: &str, value: serde_json::Value) -> Result<()> {
189        match key {
190            "max_body_size" => {
191                if let Some(size) = value.as_u64() {
192                    self.config.max_body_size = size as usize;
193                }
194            },
195            "request_timeout_ms" => {
196                if let Some(timeout) = value.as_u64() {
197                    self.config.request_timeout_ms = timeout;
198                }
199            },
200            _ => {},
201        }
202        Ok(())
203    }
204}
205
206/// HTTP connection implementation
207pub struct HttpConnection {
208    info: ConnectionInfo,
209    remote_addr: String,
210    connected: AtomicBool,
211    stats: Arc<Mutex<ConnectionStats>>,
212    transport_stats: Arc<Mutex<TransportStats>>,
213    message_queue: Arc<Mutex<mpsc::UnboundedReceiver<TransportMessage>>>,
214    #[allow(dead_code)] // Message sender for future async processing
215    message_tx: mpsc::UnboundedSender<TransportMessage>,
216}
217
218impl HttpConnection {
219    fn new(remote_addr: String, transport_stats: Arc<Mutex<TransportStats>>) -> Self {
220        let (message_tx, message_rx) = mpsc::unbounded_channel();
221
222        let info = ConnectionInfo {
223            id: uuid::Uuid::new_v4().to_string(),
224            transport_type: TransportType::Http,
225            client_id: None,
226            remote_addr: Some(remote_addr.clone()),
227            connected_at: chrono::Utc::now(),
228            security_info: None, // Would be populated based on TLS info
229        };
230
231        Self {
232            info,
233            remote_addr,
234            connected: AtomicBool::new(true),
235            stats: Arc::new(Mutex::new(ConnectionStats::default())),
236            transport_stats,
237            message_queue: Arc::new(Mutex::new(message_rx)),
238            message_tx,
239        }
240    }
241}
242
243#[async_trait]
244impl TransportConnection for HttpConnection {
245    fn connection_info(&self) -> &ConnectionInfo {
246        &self.info
247    }
248
249    async fn send(&mut self, message: TransportMessage) -> Result<()> {
250        if !self.connected.load(Ordering::Relaxed) {
251            return Err(anyhow::anyhow!("Connection closed"));
252        }
253
254        // In real implementation, send HTTP request/response
255        debug!("HTTP send to {}: {}", self.remote_addr, message.id);
256
257        let mut stats = self.stats.lock().await;
258        stats.messages_sent += 1;
259        stats.bytes_sent += serde_json::to_vec(&message.payload)?.len() as u64;
260        drop(stats);
261
262        let mut transport_stats = self.transport_stats.lock().await;
263        transport_stats.messages_sent += 1;
264        drop(transport_stats);
265
266        Ok(())
267    }
268
269    async fn receive(&mut self) -> Result<Option<TransportMessage>> {
270        if !self.connected.load(Ordering::Relaxed) {
271            return Ok(None);
272        }
273
274        // Check message queue
275        let mut queue = self.message_queue.lock().await;
276        match queue.recv().await {
277            Some(message) => {
278                let mut stats = self.stats.lock().await;
279                stats.messages_received += 1;
280                drop(stats);
281
282                let mut transport_stats = self.transport_stats.lock().await;
283                transport_stats.messages_received += 1;
284                drop(transport_stats);
285
286                Ok(Some(message))
287            },
288            None => Ok(None),
289        }
290    }
291
292    async fn close(&mut self) -> Result<()> {
293        self.connected.store(false, Ordering::Relaxed);
294
295        let mut transport_stats = self.transport_stats.lock().await;
296        transport_stats.active_connections = transport_stats.active_connections.saturating_sub(1);
297        drop(transport_stats);
298
299        info!("Closed HTTP connection to {}", self.remote_addr);
300        Ok(())
301    }
302
303    fn is_connected(&self) -> bool {
304        self.connected.load(Ordering::Relaxed)
305    }
306
307    fn get_stats(&self) -> ConnectionStats {
308        if let Ok(stats) = self.stats.try_lock() {
309            stats.clone()
310        } else {
311            ConnectionStats::default()
312        }
313    }
314
315    async fn set_option(&mut self, key: &str, value: serde_json::Value) -> Result<()> {
316        if key == "client_id" {
317            if let Some(id) = value.as_str() {
318                self.info.client_id = Some(id.to_string());
319            }
320        }
321        Ok(())
322    }
323}
324
325/// HTTP RPC handler
326async fn handle_rpc(
327    State(stats): State<Arc<Mutex<TransportStats>>>,
328    Json(payload): Json<serde_json::Value>,
329) -> Result<Json<serde_json::Value>, (axum::http::StatusCode, String)> {
330    // Update stats
331    let mut s = stats.lock().await;
332    s.messages_received += 1;
333
334    // Echo back for now (would process RPC in real impl)
335    Ok(Json(serde_json::json!({
336        "jsonrpc": "2.0",
337        "result": payload,
338        "id": payload.get("id").cloned().unwrap_or(serde_json::Value::Null)
339    })))
340}
341
342// Note: Additional features to implement:
343// 1. Proper request/response handling
344// 2. TLS support with rustls or native-tls
345// 3. Connection pooling for client mode
346// 4. Proper error handling and retries
347// 5. Request routing and middleware support