@zap-js/server 0.0.2 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/proxy.rs DELETED
@@ -1,436 +0,0 @@
1
- //! Proxy handler that forwards requests to TypeScript via IPC
2
- //!
3
- //! When a TypeScript handler is routed, this handler:
4
- //! 1. Serializes the request to IPC protocol
5
- //! 2. Sends to TypeScript via Unix socket (using connection pool)
6
- //! 3. Waits for response with timeout
7
- //! 4. Converts response back to HTTP
8
- //!
9
- //! Supports both regular and streaming responses from TypeScript handlers.
10
-
11
- use crate::connection_pool::ConnectionPool;
12
- use crate::error::{ZapError, ZapResult};
13
- use crate::handler::Handler;
14
- use crate::ipc::{IpcClient, IpcEncoding, IpcMessage, IpcRequest};
15
- use crate::request_id;
16
- use crate::response::{StreamingResponse, ZapResponse};
17
- use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
18
- use std::future::Future;
19
- use std::pin::Pin;
20
- use std::sync::Arc;
21
- use tracing::{debug, error, info, warn};
22
- use zap_core::Request;
23
-
24
- /// Handler that proxies requests to TypeScript via IPC
25
- pub struct ProxyHandler {
26
- /// Unique identifier for this handler
27
- handler_id: String,
28
-
29
- /// Path to the Unix socket for IPC communication
30
- ipc_socket_path: Arc<String>,
31
-
32
- /// Request timeout in seconds
33
- timeout_secs: u64,
34
-
35
- /// Optional connection pool (if None, uses global pool or creates per-request connections)
36
- connection_pool: Option<Arc<ConnectionPool>>,
37
- }
38
-
39
- impl ProxyHandler {
40
- /// Create a new proxy handler
41
- pub fn new(handler_id: String, ipc_socket_path: String) -> Self {
42
- Self {
43
- handler_id,
44
- ipc_socket_path: Arc::new(ipc_socket_path),
45
- timeout_secs: 30,
46
- connection_pool: None,
47
- }
48
- }
49
-
50
- /// Create with custom timeout
51
- pub fn with_timeout(
52
- handler_id: String,
53
- ipc_socket_path: String,
54
- timeout_secs: u64,
55
- ) -> Self {
56
- Self {
57
- handler_id,
58
- ipc_socket_path: Arc::new(ipc_socket_path),
59
- timeout_secs,
60
- connection_pool: None,
61
- }
62
- }
63
-
64
- /// Create with a specific connection pool
65
- pub fn with_pool(
66
- handler_id: String,
67
- ipc_socket_path: String,
68
- pool: Arc<ConnectionPool>,
69
- ) -> Self {
70
- Self {
71
- handler_id,
72
- ipc_socket_path: Arc::new(ipc_socket_path),
73
- timeout_secs: 30,
74
- connection_pool: Some(pool),
75
- }
76
- }
77
-
78
- /// Create with custom timeout and connection pool
79
- pub fn with_timeout_and_pool(
80
- handler_id: String,
81
- ipc_socket_path: String,
82
- timeout_secs: u64,
83
- pool: Arc<ConnectionPool>,
84
- ) -> Self {
85
- Self {
86
- handler_id,
87
- ipc_socket_path: Arc::new(ipc_socket_path),
88
- timeout_secs,
89
- connection_pool: Some(pool),
90
- }
91
- }
92
-
93
- /// Make an IPC request to the TypeScript handler
94
- /// Returns the response which may be a regular response or a streaming start message
95
- async fn invoke_handler(&self, request: IpcRequest) -> ZapResult<ZapResponse> {
96
- debug!(
97
- "📤 Invoking TypeScript handler: {} for {} {}",
98
- self.handler_id, request.method, request.path
99
- );
100
-
101
- // Create invocation message
102
- let msg = IpcMessage::InvokeHandler {
103
- handler_id: self.handler_id.clone(),
104
- request,
105
- };
106
-
107
- // For streaming support, we need a dedicated connection that we can keep reading from
108
- // We can't use the connection pool for this because streaming needs multiple reads
109
- // So we create a dedicated connection for the entire request lifecycle
110
- let response = self.invoke_with_streaming_support(msg).await?;
111
-
112
- debug!("📥 Received response from TypeScript handler");
113
-
114
- Ok(response)
115
- }
116
-
117
- /// Invoke handler with full streaming support
118
- /// This uses a dedicated connection so we can handle streaming responses
119
- async fn invoke_with_streaming_support(&self, msg: IpcMessage) -> ZapResult<ZapResponse> {
120
- // Connect to TypeScript's IPC server
121
- let mut client = IpcClient::connect_with_encoding(
122
- self.ipc_socket_path.as_str(),
123
- IpcEncoding::MessagePack,
124
- )
125
- .await
126
- .map_err(|e| {
127
- error!("Failed to connect to IPC: {}", e);
128
- e
129
- })?;
130
-
131
- // Send the invocation
132
- client.send_message(msg).await.map_err(|e| {
133
- error!("Failed to send IPC message: {}", e);
134
- e
135
- })?;
136
-
137
- // Wait for first response with timeout
138
- let timeout_duration = std::time::Duration::from_secs(self.timeout_secs);
139
-
140
- let first_response = tokio::time::timeout(timeout_duration, client.recv_message())
141
- .await
142
- .map_err(|_| {
143
- warn!(
144
- "Handler {} timed out after {}s",
145
- self.handler_id, self.timeout_secs
146
- );
147
- ZapError::timeout(
148
- format!(
149
- "Handler {} did not respond within {}s",
150
- self.handler_id, self.timeout_secs
151
- ),
152
- self.timeout_secs * 1000,
153
- )
154
- })?
155
- .map_err(|e| {
156
- error!("IPC connection error: {}", e);
157
- ZapError::ipc("Connection error")
158
- })?
159
- .ok_or_else(|| {
160
- error!("Received None from IPC channel");
161
- ZapError::ipc("No response from handler")
162
- })?;
163
-
164
- // Handle the response based on type
165
- match first_response {
166
- // Regular handler response - return immediately
167
- IpcMessage::HandlerResponse {
168
- handler_id: _,
169
- status,
170
- headers,
171
- body,
172
- } => {
173
- debug!("Converting IPC response to HTTP response (status: {})", status);
174
-
175
- let status_code = zap_core::StatusCode::new(status);
176
- let mut zap_response = zap_core::Response::with_status(status_code).body(body);
177
-
178
- for (key, value) in headers {
179
- zap_response = zap_response.header(key, value);
180
- }
181
-
182
- Ok(ZapResponse::Custom(zap_response))
183
- }
184
-
185
- // Streaming response - continue reading chunks until StreamEnd
186
- IpcMessage::StreamStart {
187
- stream_id,
188
- status,
189
- headers,
190
- } => {
191
- info!("Starting streaming response: {} (status: {})", stream_id, status);
192
- self.handle_streaming_response(&mut client, stream_id, status, headers)
193
- .await
194
- }
195
-
196
- // Error response
197
- IpcMessage::Error { code, message, .. } => {
198
- error!(
199
- "Handler {} returned error: {} - {}",
200
- self.handler_id, code, message
201
- );
202
- Err(ZapError::handler_with_id(
203
- format!("{}: {}", code, message),
204
- &self.handler_id,
205
- ))
206
- }
207
-
208
- // Unexpected message type
209
- other => {
210
- error!(
211
- "Handler {} returned unexpected message type: {:?}",
212
- self.handler_id, other
213
- );
214
- Err(ZapError::handler_with_id(
215
- "Invalid response type from TypeScript handler",
216
- &self.handler_id,
217
- ))
218
- }
219
- }
220
- }
221
-
222
- /// Handle a streaming response by collecting all chunks until StreamEnd
223
- async fn handle_streaming_response(
224
- &self,
225
- client: &mut IpcClient,
226
- stream_id: String,
227
- status: u16,
228
- headers: std::collections::HashMap<String, String>,
229
- ) -> ZapResult<ZapResponse> {
230
- let mut streaming_response = StreamingResponse::new(status, headers);
231
- let timeout_duration = std::time::Duration::from_secs(self.timeout_secs);
232
-
233
- loop {
234
- // Read next message with timeout
235
- let msg = tokio::time::timeout(timeout_duration, client.recv_message())
236
- .await
237
- .map_err(|_| {
238
- warn!(
239
- "Streaming response {} timed out after {}s",
240
- stream_id, self.timeout_secs
241
- );
242
- ZapError::timeout(
243
- format!(
244
- "Streaming response {} did not complete within {}s",
245
- stream_id, self.timeout_secs
246
- ),
247
- self.timeout_secs * 1000,
248
- )
249
- })?
250
- .map_err(|e| {
251
- error!("IPC connection error during streaming: {}", e);
252
- ZapError::ipc("Connection error during streaming")
253
- })?
254
- .ok_or_else(|| {
255
- error!("Connection closed during streaming");
256
- ZapError::ipc("Connection closed during streaming")
257
- })?;
258
-
259
- match msg {
260
- // Chunk received - decode and add to response
261
- IpcMessage::StreamChunk {
262
- stream_id: chunk_stream_id,
263
- data,
264
- } => {
265
- if chunk_stream_id != stream_id {
266
- warn!(
267
- "Received chunk for wrong stream: expected {}, got {}",
268
- stream_id, chunk_stream_id
269
- );
270
- continue;
271
- }
272
-
273
- // Decode base64 data
274
- match BASE64.decode(&data) {
275
- Ok(decoded) => {
276
- debug!(
277
- "Received chunk for stream {}: {} bytes",
278
- stream_id,
279
- decoded.len()
280
- );
281
- streaming_response.add_chunk(decoded);
282
- }
283
- Err(e) => {
284
- error!("Failed to decode base64 chunk: {}", e);
285
- // Try treating as raw UTF-8
286
- streaming_response.add_chunk(data.into_bytes());
287
- }
288
- }
289
- }
290
-
291
- // Stream ended - return the collected response
292
- IpcMessage::StreamEnd {
293
- stream_id: end_stream_id,
294
- } => {
295
- if end_stream_id != stream_id {
296
- warn!(
297
- "Received end for wrong stream: expected {}, got {}",
298
- stream_id, end_stream_id
299
- );
300
- continue;
301
- }
302
-
303
- info!(
304
- "Streaming response {} completed: {} chunks, {} bytes total",
305
- stream_id,
306
- streaming_response.chunks.len(),
307
- streaming_response.body_bytes().len()
308
- );
309
- return Ok(ZapResponse::Stream(streaming_response));
310
- }
311
-
312
- // Error during streaming
313
- IpcMessage::Error { code, message, .. } => {
314
- error!(
315
- "Error during streaming {}: {} - {}",
316
- stream_id, code, message
317
- );
318
- return Err(ZapError::handler_with_id(
319
- format!("Streaming error: {}: {}", code, message),
320
- &self.handler_id,
321
- ));
322
- }
323
-
324
- // Unexpected message
325
- other => {
326
- warn!(
327
- "Unexpected message during streaming {}: {:?}",
328
- stream_id, other
329
- );
330
- // Continue waiting for proper stream messages
331
- }
332
- }
333
- }
334
- }
335
-
336
- /// Invoke handler using connection pool (for non-streaming responses)
337
- #[allow(dead_code)]
338
- async fn invoke_with_pool(
339
- &self,
340
- pool: &ConnectionPool,
341
- msg: IpcMessage,
342
- ) -> ZapResult<IpcMessage> {
343
- let timeout_duration = std::time::Duration::from_secs(self.timeout_secs);
344
-
345
- tokio::time::timeout(timeout_duration, pool.send_recv(msg))
346
- .await
347
- .map_err(|_| {
348
- warn!(
349
- "Handler {} timed out after {}s",
350
- self.handler_id, self.timeout_secs
351
- );
352
- ZapError::timeout(
353
- format!(
354
- "Handler {} did not respond within {}s",
355
- self.handler_id, self.timeout_secs
356
- ),
357
- self.timeout_secs * 1000,
358
- )
359
- })?
360
- }
361
- }
362
-
363
- impl Handler for ProxyHandler {
364
- fn handle<'a>(
365
- &'a self,
366
- req: Request<'a>,
367
- ) -> Pin<Box<dyn Future<Output = Result<ZapResponse, ZapError>> + Send + 'a>> {
368
- Box::pin(async move {
369
- // Convert Rust request to IPC request format
370
- let body_bytes = req.body();
371
- let body_string = String::from_utf8_lossy(body_bytes).to_string();
372
-
373
- // Use the request data that's already been parsed
374
- // Get or generate request ID for correlation
375
- let headers_map: std::collections::HashMap<String, String> = req
376
- .headers()
377
- .iter()
378
- .map(|(k, v)| (k.to_string(), v.to_string()))
379
- .collect();
380
- let request_id = request_id::get_or_generate(&headers_map);
381
-
382
- let ipc_request = IpcRequest {
383
- request_id,
384
- method: req.method().to_string(),
385
- path: req.path().to_string(), // Already includes query string
386
- path_only: req.path_only().to_string(),
387
- query: req
388
- .query_params()
389
- .iter()
390
- .map(|(k, v)| (k.to_string(), v.to_string()))
391
- .collect(),
392
- params: req
393
- .params()
394
- .iter()
395
- .map(|(k, v)| (k.to_string(), v.to_string()))
396
- .collect(),
397
- headers: headers_map,
398
- body: body_string,
399
- cookies: req
400
- .cookies()
401
- .iter()
402
- .map(|(k, v)| (k.to_string(), v.to_string()))
403
- .collect(),
404
- };
405
-
406
- // Invoke TypeScript handler via IPC (handles both regular and streaming responses)
407
- self.invoke_handler(ipc_request).await
408
- })
409
- }
410
- }
411
-
412
- #[cfg(test)]
413
- mod tests {
414
- use super::*;
415
-
416
- #[test]
417
- fn test_proxy_handler_creation() {
418
- let handler = ProxyHandler::new(
419
- "handler_0".to_string(),
420
- "/tmp/zap.sock".to_string(),
421
- );
422
- assert_eq!(handler.handler_id, "handler_0");
423
- assert_eq!(handler.timeout_secs, 30);
424
- }
425
-
426
- #[test]
427
- fn test_proxy_handler_with_custom_timeout() {
428
- let handler = ProxyHandler::with_timeout(
429
- "handler_1".to_string(),
430
- "/tmp/zap.sock".to_string(),
431
- 60,
432
- );
433
- assert_eq!(handler.handler_id, "handler_1");
434
- assert_eq!(handler.timeout_secs, 60);
435
- }
436
- }