1use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11use std::sync::{Arc, Mutex};
12use tungstenite::{connect, Message};
13use url::Url;
14
15use crate::error::{Error, Result};
16use crate::model::HttpRequest;
17
18pub mod phase {
24 pub const REQUEST: &str = "REQUEST";
25 pub const RESPONSE: &str = "RESPONSE";
26 pub const RESPONSE_STREAM: &str = "RESPONSE_STREAM";
27 pub const INBOUND_STREAM: &str = "INBOUND_STREAM";
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct BreakpointMatcherRegistration {
38 pub http_request: HttpRequest,
39 pub phases: Vec<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub client_id: Option<String>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct BreakpointMatcherResponse {
47 pub id: String,
48 pub phases: Vec<String>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct BreakpointMatcherEntry {
55 pub id: String,
56 pub http_request: Value,
57 pub phases: Vec<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub client_id: Option<String>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct BreakpointMatcherList {
65 pub matchers: Vec<BreakpointMatcherEntry>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct PausedStreamFrame {
72 pub correlation_id: String,
73 #[serde(default)]
74 pub stream_id: String,
75 #[serde(default)]
76 pub sequence_number: i64,
77 #[serde(default)]
78 pub direction: String,
79 #[serde(default)]
80 pub phase: String,
81 #[serde(default)]
82 pub body: String,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub request_method: Option<String>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub request_path: Option<String>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub breakpoint_id: Option<String>,
89}
90
91impl PausedStreamFrame {
92 pub fn body_bytes(&self) -> std::result::Result<Vec<u8>, base64::DecodeError> {
94 BASE64.decode(&self.body)
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct StreamFrameDecision {
102 pub correlation_id: String,
103 pub action: String,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub body: Option<String>,
106}
107
108impl StreamFrameDecision {
109 pub fn continue_frame(correlation_id: impl Into<String>) -> Self {
111 Self {
112 correlation_id: correlation_id.into(),
113 action: "CONTINUE".to_string(),
114 body: None,
115 }
116 }
117
118 pub fn modify(correlation_id: impl Into<String>, body: &[u8]) -> Self {
120 Self {
121 correlation_id: correlation_id.into(),
122 action: "MODIFY".to_string(),
123 body: Some(BASE64.encode(body)),
124 }
125 }
126
127 pub fn drop_frame(correlation_id: impl Into<String>) -> Self {
129 Self {
130 correlation_id: correlation_id.into(),
131 action: "DROP".to_string(),
132 body: None,
133 }
134 }
135
136 pub fn inject(correlation_id: impl Into<String>, extra_body: &[u8]) -> Self {
138 Self {
139 correlation_id: correlation_id.into(),
140 action: "INJECT".to_string(),
141 body: Some(BASE64.encode(extra_body)),
142 }
143 }
144
145 pub fn close(correlation_id: impl Into<String>) -> Self {
147 Self {
148 correlation_id: correlation_id.into(),
149 action: "CLOSE".to_string(),
150 body: None,
151 }
152 }
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct WsEnvelope {
161 #[serde(rename = "type")]
162 pub msg_type: String,
163 pub value: String,
164}
165
166#[derive(Debug, Clone, Deserialize)]
167#[serde(rename_all = "camelCase")]
168struct WsClientIdDTO {
169 client_id: String,
170}
171
172pub type BreakpointRequestHandler = Box<dyn Fn(Value) -> Option<Value> + Send + Sync>;
183
184pub type BreakpointResponseHandler = Box<dyn Fn(Value, Value) -> Option<Value> + Send + Sync>;
188
189pub type BreakpointStreamFrameHandler =
193 Box<dyn Fn(&PausedStreamFrame) -> Option<StreamFrameDecision> + Send + Sync>;
194
195pub(crate) struct BreakpointWebSocketClient {
201 pub(crate) client_id: String,
202 socket: Arc<Mutex<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>>,
203 request_handlers: Arc<Mutex<HashMap<String, BreakpointRequestHandler>>>,
204 response_handlers: Arc<Mutex<HashMap<String, BreakpointResponseHandler>>>,
205 stream_frame_handlers: Arc<Mutex<HashMap<String, BreakpointStreamFrameHandler>>>,
206 dead: Arc<std::sync::atomic::AtomicBool>,
207 _read_thread: Option<std::thread::JoinHandle<()>>,
208}
209
210impl BreakpointWebSocketClient {
211 pub(crate) fn is_dead(&self) -> bool {
213 self.dead.load(std::sync::atomic::Ordering::Relaxed)
214 }
215
216 pub(crate) fn connect(base_url: &str) -> Result<Self> {
218 let parsed = Url::parse(base_url)
219 .map_err(|e| Error::InvalidRequest(format!("bad base URL: {e}")))?;
220
221 let ws_scheme = if parsed.scheme() == "https" { "wss" } else { "ws" };
222 let host = parsed.host_str().unwrap_or("localhost");
223 let port = parsed.port().unwrap_or(if parsed.scheme() == "https" { 443 } else { 80 });
224 let path = parsed.path().trim_end_matches('/');
225 let ws_url = format!("{ws_scheme}://{host}:{port}{path}/_mockserver_callback_websocket");
226
227 let (mut socket, _response) = connect(&ws_url)
233 .map_err(|e| Error::InvalidRequest(format!("WebSocket connect failed: {e}")))?;
234
235 let reg_msg = socket
237 .read()
238 .map_err(|e| Error::InvalidRequest(format!("WebSocket read registration: {e}")))?;
239
240 let reg_text = match reg_msg {
241 Message::Text(t) => t,
242 _ => return Err(Error::InvalidRequest("Expected text registration message".into())),
243 };
244
245 let envelope: WsEnvelope = serde_json::from_str(®_text)?;
246 let client_id = if envelope.msg_type
247 == "org.mockserver.serialization.model.WebSocketClientIdDTO"
248 {
249 let dto: WsClientIdDTO = serde_json::from_str(&envelope.value)?;
250 dto.client_id
251 } else {
252 return Err(Error::InvalidRequest(format!(
253 "Unexpected registration type: {}",
254 envelope.msg_type
255 )));
256 };
257
258 let request_handlers: Arc<Mutex<HashMap<String, BreakpointRequestHandler>>> =
259 Arc::new(Mutex::new(HashMap::new()));
260 let response_handlers: Arc<Mutex<HashMap<String, BreakpointResponseHandler>>> =
261 Arc::new(Mutex::new(HashMap::new()));
262 let stream_frame_handlers: Arc<Mutex<HashMap<String, BreakpointStreamFrameHandler>>> =
263 Arc::new(Mutex::new(HashMap::new()));
264
265 let socket = Arc::new(Mutex::new(socket));
266 let dead = Arc::new(std::sync::atomic::AtomicBool::new(false));
267
268 let rh = Arc::clone(&request_handlers);
270 let resh = Arc::clone(&response_handlers);
271 let sfh = Arc::clone(&stream_frame_handlers);
272 let sock = Arc::clone(&socket);
273 let dead_flag = Arc::clone(&dead);
274
275 let read_thread = std::thread::spawn(move || {
276 read_loop(sock, rh, resh, sfh, dead_flag);
277 });
278
279 Ok(Self {
280 client_id,
281 socket,
282 request_handlers,
283 response_handlers,
284 stream_frame_handlers,
285 dead,
286 _read_thread: Some(read_thread),
287 })
288 }
289
290 pub(crate) fn set_request_handler(&self, breakpoint_id: &str, handler: BreakpointRequestHandler) {
291 self.request_handlers
292 .lock()
293 .unwrap()
294 .insert(breakpoint_id.to_string(), handler);
295 }
296
297 pub(crate) fn set_response_handler(&self, breakpoint_id: &str, handler: BreakpointResponseHandler) {
298 self.response_handlers
299 .lock()
300 .unwrap()
301 .insert(breakpoint_id.to_string(), handler);
302 }
303
304 pub(crate) fn set_stream_frame_handler(
305 &self,
306 breakpoint_id: &str,
307 handler: BreakpointStreamFrameHandler,
308 ) {
309 self.stream_frame_handlers
310 .lock()
311 .unwrap()
312 .insert(breakpoint_id.to_string(), handler);
313 }
314
315 pub(crate) fn remove_handlers(&self, breakpoint_id: &str) {
316 self.request_handlers.lock().unwrap().remove(breakpoint_id);
317 self.response_handlers.lock().unwrap().remove(breakpoint_id);
318 self.stream_frame_handlers
319 .lock()
320 .unwrap()
321 .remove(breakpoint_id);
322 }
323
324 pub(crate) fn clear_handlers(&self) {
325 self.request_handlers.lock().unwrap().clear();
326 self.response_handlers.lock().unwrap().clear();
327 self.stream_frame_handlers.lock().unwrap().clear();
328 }
329
330 pub(crate) fn close(&self) {
331 if let Ok(mut sock) = self.socket.lock() {
332 let _ = sock.close(None);
333 }
334 }
335}
336
337struct ReadLoopDeadGuard(Arc<std::sync::atomic::AtomicBool>);
343impl Drop for ReadLoopDeadGuard {
344 fn drop(&mut self) {
345 self.0.store(true, std::sync::atomic::Ordering::Relaxed);
346 }
347}
348
349fn read_loop(
350 socket: Arc<Mutex<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>>,
351 request_handlers: Arc<Mutex<HashMap<String, BreakpointRequestHandler>>>,
352 response_handlers: Arc<Mutex<HashMap<String, BreakpointResponseHandler>>>,
353 stream_frame_handlers: Arc<Mutex<HashMap<String, BreakpointStreamFrameHandler>>>,
354 dead: Arc<std::sync::atomic::AtomicBool>,
355) {
356 let _guard = ReadLoopDeadGuard(dead);
357 loop {
358 let msg = {
359 let mut sock = match socket.lock() {
360 Ok(s) => s,
361 Err(e) => {
362 eprintln!("mockserver: breakpoint ws read loop terminated: mutex poisoned: {e}");
363 return;
364 }
365 };
366 match sock.read() {
367 Ok(m) => m,
368 Err(e) => {
369 eprintln!("mockserver: breakpoint ws read loop terminated: {e}");
370 return;
371 }
372 }
373 };
374
375 let text = match msg {
376 Message::Text(t) => t,
377 Message::Close(_) => {
378 eprintln!("mockserver: breakpoint ws read loop terminated: received close frame");
379 return;
380 }
381 _ => continue,
382 };
383
384 let envelope: WsEnvelope = match serde_json::from_str(&text) {
385 Ok(e) => e,
386 Err(_) => continue,
387 };
388
389 match envelope.msg_type.as_str() {
390 "org.mockserver.model.HttpRequest" => {
391 handle_request(
392 &envelope.value,
393 &request_handlers,
394 &socket,
395 );
396 }
397 "org.mockserver.model.HttpRequestAndHttpResponse" => {
398 handle_response(
399 &envelope.value,
400 &response_handlers,
401 &socket,
402 );
403 }
404 "org.mockserver.serialization.model.PausedStreamFrameDTO" => {
405 handle_stream_frame(
406 &envelope.value,
407 &stream_frame_handlers,
408 &socket,
409 );
410 }
411 "org.mockserver.serialization.model.WebSocketClientIdDTO" => {
412 }
414 _ => {}
415 }
416 }
417}
418
419fn handle_request(
420 value_json: &str,
421 handlers: &Arc<Mutex<HashMap<String, BreakpointRequestHandler>>>,
422 socket: &Arc<Mutex<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>>,
423) {
424 if let Some((type_name, result)) = route_request(value_json, handlers) {
425 send_envelope(socket, &type_name, &result);
426 }
427}
428
429fn handle_response(
430 value_json: &str,
431 handlers: &Arc<Mutex<HashMap<String, BreakpointResponseHandler>>>,
432 socket: &Arc<Mutex<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>>,
433) {
434 if let Some((type_name, result)) = route_response(value_json, handlers) {
435 send_envelope(socket, &type_name, &result);
436 }
437}
438
439fn handle_stream_frame(
440 value_json: &str,
441 handlers: &Arc<Mutex<HashMap<String, BreakpointStreamFrameHandler>>>,
442 socket: &Arc<Mutex<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>>,
443) {
444 if let Some((type_name, decision)) = route_stream_frame(value_json, handlers) {
445 send_envelope(socket, &type_name, &decision);
446 }
447}
448
449pub fn route_request(
456 value_json: &str,
457 handlers: &Arc<Mutex<HashMap<String, BreakpointRequestHandler>>>,
458) -> Option<(String, Value)> {
459 let request: Value = serde_json::from_str(value_json).ok()?;
460
461 let correlation_id = extract_header(&request, "WebSocketCorrelationId");
462 let breakpoint_id = extract_header(&request, "X-MockServer-BreakpointId");
463
464 let mut result: Option<Value> = None;
465 if let Some(bp_id) = &breakpoint_id {
466 if let Ok(h) = handlers.lock() {
467 if let Some(handler) = h.get(bp_id) {
468 result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
469 handler(request.clone())
470 }))
471 .unwrap_or(None);
472 }
473 }
474 }
475
476 let mut result = result.unwrap_or_else(|| request.clone());
478
479 let type_name = if result.get("statusCode").is_some() {
481 "org.mockserver.model.HttpResponse"
482 } else {
483 "org.mockserver.model.HttpRequest"
484 };
485
486 if let Some(corr) = &correlation_id {
487 set_header(&mut result, "WebSocketCorrelationId", corr);
488 }
489
490 Some((type_name.to_string(), result))
491}
492
493pub fn route_response(
496 value_json: &str,
497 handlers: &Arc<Mutex<HashMap<String, BreakpointResponseHandler>>>,
498) -> Option<(String, Value)> {
499 let req_and_resp: Value = serde_json::from_str(value_json).ok()?;
500
501 let http_request = req_and_resp.get("httpRequest").cloned().unwrap_or(Value::Null);
502 let http_response = req_and_resp.get("httpResponse").cloned().unwrap_or(Value::Null);
503
504 let correlation_id = extract_header(&http_request, "WebSocketCorrelationId");
505 let breakpoint_id = extract_header(&http_request, "X-MockServer-BreakpointId");
506
507 let mut result: Option<Value> = None;
508 if let Some(bp_id) = &breakpoint_id {
509 if let Ok(h) = handlers.lock() {
510 if let Some(handler) = h.get(bp_id) {
511 result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
512 handler(http_request.clone(), http_response.clone())
513 }))
514 .unwrap_or(None);
515 }
516 }
517 }
518
519 let mut result = result.unwrap_or(http_response);
520
521 if let Some(corr) = &correlation_id {
522 set_header(&mut result, "WebSocketCorrelationId", corr);
523 }
524
525 Some(("org.mockserver.model.HttpResponse".to_string(), result))
526}
527
528pub fn route_stream_frame(
531 value_json: &str,
532 handlers: &Arc<Mutex<HashMap<String, BreakpointStreamFrameHandler>>>,
533) -> Option<(String, StreamFrameDecision)> {
534 let frame: PausedStreamFrame = serde_json::from_str(value_json).ok()?;
535
536 let mut decision: Option<StreamFrameDecision> = None;
537 if let Some(bp_id) = &frame.breakpoint_id {
538 if let Ok(h) = handlers.lock() {
539 if let Some(handler) = h.get(bp_id) {
540 decision = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
541 handler(&frame)
542 }))
543 .unwrap_or(None);
544 }
545 }
546 }
547
548 let mut decision = decision.unwrap_or_else(|| StreamFrameDecision::continue_frame(&frame.correlation_id));
549 decision.correlation_id = frame.correlation_id;
550
551 Some((
552 "org.mockserver.serialization.model.StreamFrameDecisionDTO".to_string(),
553 decision,
554 ))
555}
556
557fn send_envelope<T: Serialize>(
558 socket: &Arc<Mutex<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>>,
559 type_name: &str,
560 value: &T,
561) {
562 let value_json = match serde_json::to_string(value) {
563 Ok(j) => j,
564 Err(_) => return,
565 };
566 let envelope = WsEnvelope {
567 msg_type: type_name.to_string(),
568 value: value_json,
569 };
570 let env_json = match serde_json::to_string(&envelope) {
571 Ok(j) => j,
572 Err(_) => return,
573 };
574 if let Ok(mut sock) = socket.lock() {
575 let _ = sock.send(Message::Text(env_json));
576 }
577}
578
579pub fn extract_header(obj: &Value, name: &str) -> Option<String> {
585 let headers = obj.get("headers")?.as_object()?;
586 let name_lower = name.to_lowercase();
587 for (k, v) in headers {
588 if k.to_lowercase() == name_lower {
589 if let Some(arr) = v.as_array() {
590 return arr.first()?.as_str().map(|s| s.to_string());
591 }
592 return v.as_str().map(|s| s.to_string());
593 }
594 }
595 None
596}
597
598pub fn set_header(obj: &mut Value, name: &str, value: &str) {
600 if let Some(obj_map) = obj.as_object_mut() {
601 let headers = obj_map
602 .entry("headers")
603 .or_insert_with(|| Value::Object(serde_json::Map::new()));
604 if let Some(h) = headers.as_object_mut() {
605 h.insert(
606 name.to_string(),
607 serde_json::json!([value]),
608 );
609 }
610 }
611}