@zap-js/server 0.0.2 → 0.0.4
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/index.js +18 -14
- package/package.json +4 -9
- package/src/bin/zap.rs +0 -154
- package/src/config.rs +0 -253
- package/src/connection_pool.rs +0 -404
- package/src/error.rs +0 -380
- package/src/handler.rs +0 -89
- package/src/ipc.js +0 -10
- package/src/ipc.rs +0 -499
- package/src/lib.rs +0 -433
- package/src/metrics.rs +0 -264
- package/src/proxy.rs +0 -436
- package/src/reliability.rs +0 -917
- package/src/request.rs +0 -60
- package/src/request_id.rs +0 -97
- package/src/response.rs +0 -182
- package/src/rpc.js +0 -14
- package/src/server.rs +0 -597
- package/src/static.rs +0 -572
- package/src/types.js +0 -21
- package/src/utils.rs +0 -18
- package/src/websocket.rs +0 -429
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
|
-
}
|