Skip to main content

monocoque/zmq/
req.rs

1//! REQ socket implementation.
2
3use super::common::channel_to_io_error;
4use bytes::Bytes;
5use monocoque_core::monitor::{SocketEvent, SocketEventSender, SocketMonitor, create_monitor};
6use monocoque_core::rt::TcpStream;
7use monocoque_zmtp::SocketType;
8use monocoque_zmtp::req::ReqSocket as InternalReq;
9use std::io;
10
11/// A REQ socket for synchronous request-reply patterns.
12///
13/// REQ sockets enforce strict alternation between send and receive:
14/// - Must call `send()` before `recv()`
15/// - Must call `recv()` before next `send()`
16///
17/// They're used for:
18/// - Synchronous RPC clients
19/// - Request-reply protocols
20/// - Client-server communication
21///
22/// ## ZeroMQ Compatibility
23///
24/// Compatible with `zmq::REQ` and `zmq::REP` sockets from libzmq.
25///
26/// ## Example
27///
28/// ```rust,no_run
29/// use monocoque::zmq::ReqSocket;
30/// use bytes::Bytes;
31///
32/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
33/// // Connect to server
34/// let mut socket = ReqSocket::connect("127.0.0.1:5555").await?;
35///
36/// // Send request
37/// socket.send(vec![Bytes::from("REQUEST")]).await?;
38///
39/// // Must receive before next send
40/// if let Ok(Some(reply)) = socket.recv().await {
41///     println!("Got reply: {:?}", reply);
42/// }
43///
44/// // Now can send again
45/// socket.send(vec![Bytes::from("ANOTHER")]).await?;
46/// if let Ok(Some(reply)) = socket.recv().await {
47///     println!("Got reply: {:?}", reply);
48/// }
49/// # Ok(())
50/// # }
51/// ```
52pub struct ReqSocket<S = TcpStream>
53where
54    S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
55{
56    inner: InternalReq<S>,
57    monitor: Option<SocketEventSender>,
58}
59
60impl ReqSocket {
61    /// Connect to a ZeroMQ peer and create a REQ socket.
62    ///
63    /// Supports both TCP and IPC endpoints:
64    /// - TCP: `"tcp://127.0.0.1:5555"` or `"127.0.0.1:5555"`
65    /// - IPC: `"ipc:///tmp/socket.sock"` (Unix only)
66    ///
67    /// # Arguments
68    ///
69    /// * `endpoint` - Endpoint to connect to
70    ///
71    /// # Errors
72    ///
73    /// Returns an error if the connection or handshake fails.
74    ///
75    /// # Example
76    ///
77    /// ```rust,no_run
78    /// use monocoque::zmq::ReqSocket;
79    ///
80    /// # async fn example() -> std::io::Result<()> {
81    /// let socket = ReqSocket::connect("tcp://127.0.0.1:5555").await?;
82    /// # Ok(())
83    /// # }
84    /// ```
85    pub async fn connect(endpoint: &str) -> io::Result<Self> {
86        // Try parsing as endpoint, fall back to raw address
87        let addr = if let Ok(monocoque_core::endpoint::Endpoint::Tcp(a)) =
88            monocoque_core::endpoint::Endpoint::parse(endpoint)
89        {
90            a
91        } else {
92            endpoint
93                .parse::<std::net::SocketAddr>()
94                .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
95        };
96
97        let sock = Self {
98            inner: InternalReq::connect(addr).await?,
99            monitor: None,
100        };
101        sock.emit_event(SocketEvent::Connected(
102            monocoque_core::endpoint::Endpoint::Tcp(addr),
103        ));
104        Ok(sock)
105    }
106
107    /// Check if the socket is currently connected.
108    #[inline]
109    pub fn is_connected(&self) -> bool {
110        self.inner.is_connected()
111    }
112
113    /// Try to reconnect to the stored endpoint.
114    pub async fn try_reconnect(&mut self) -> io::Result<()> {
115        self.inner.try_reconnect().await
116    }
117
118    /// Send with automatic reconnection on network error.
119    pub async fn send_with_reconnect(&mut self, msg: Vec<bytes::Bytes>) -> io::Result<()> {
120        self.inner.send_with_reconnect(msg).await
121    }
122
123    /// Receive with automatic reconnection on EOF or network error.
124    pub async fn recv_with_reconnect(&mut self) -> io::Result<Option<Vec<bytes::Bytes>>> {
125        self.inner.recv_with_reconnect().await
126    }
127
128    /// Connect to a ZeroMQ peer with custom socket options.
129    ///
130    /// This allows configuring timeouts and other options before connection.
131    ///
132    /// # Example
133    ///
134    /// ```rust,no_run
135    /// use monocoque::zmq::ReqSocket;
136    /// use monocoque::SocketOptions;
137    /// use std::time::Duration;
138    ///
139    /// # async fn example() -> std::io::Result<()> {
140    /// let options = SocketOptions::default()
141    ///     .with_send_timeout(Duration::from_secs(5))
142    ///     .with_recv_timeout(Duration::from_secs(10));
143    ///
144    /// let socket = ReqSocket::connect_with_options(
145    ///     "tcp://127.0.0.1:5555",
146    ///     options
147    /// ).await?;
148    /// # Ok(())
149    /// # }
150    /// ```
151    pub async fn connect_with_options(
152        endpoint: &str,
153        options: monocoque_core::options::SocketOptions,
154    ) -> io::Result<Self> {
155        let addr = if let Ok(monocoque_core::endpoint::Endpoint::Tcp(a)) =
156            monocoque_core::endpoint::Endpoint::parse(endpoint)
157        {
158            a
159        } else {
160            endpoint
161                .parse::<std::net::SocketAddr>()
162                .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
163        };
164
165        let stream = TcpStream::connect(addr).await?;
166        let sock = Self::from_tcp_with_options(stream, options).await?;
167        sock.emit_event(SocketEvent::Connected(
168            monocoque_core::endpoint::Endpoint::Tcp(addr),
169        ));
170        Ok(sock)
171    }
172
173    /// Connect to a ZeroMQ peer via IPC (Unix domain sockets).
174    ///
175    /// Unix-only. Accepts IPC paths with or without `ipc://` prefix.
176    ///
177    /// # Example
178    ///
179    /// ```rust,no_run
180    /// # #[cfg(unix)]
181    /// # async fn example() -> std::io::Result<()> {
182    /// use monocoque::zmq::ReqSocket;
183    ///
184    /// let socket = ReqSocket::connect_ipc("/tmp/req.sock").await?;
185    /// # Ok(())
186    /// # }
187    /// ```
188    #[cfg(unix)]
189    pub async fn connect_ipc(path: &str) -> io::Result<ReqSocket<monocoque_core::rt::UnixStream>> {
190        use std::path::PathBuf;
191
192        let clean_path = path.strip_prefix("ipc://").unwrap_or(path);
193        let ipc_path = PathBuf::from(clean_path);
194
195        let stream = monocoque_core::ipc::connect(&ipc_path).await?;
196        let sock = ReqSocket::from_unix_stream(stream).await?;
197        sock.emit_event(SocketEvent::Connected(
198            monocoque_core::endpoint::Endpoint::Ipc(ipc_path),
199        ));
200        Ok(sock)
201    }
202
203    /// Create a REQ socket from a TCP stream with TCP_NODELAY enabled.
204    pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
205        Ok(Self {
206            inner: InternalReq::from_tcp(stream).await?,
207            monitor: None,
208        })
209    }
210
211    /// Create a REQ socket from a TCP stream with custom socket options.
212    pub async fn from_tcp_with_options(
213        stream: TcpStream,
214        options: monocoque_core::options::SocketOptions,
215    ) -> io::Result<Self> {
216        Ok(Self {
217            inner: InternalReq::from_tcp_with_options(stream, options).await?,
218            monitor: None,
219        })
220    }
221
222    /// Create a REQ socket from any stream with custom options.
223    pub async fn with_options<Stream>(
224        stream: Stream,
225        options: monocoque_core::options::SocketOptions,
226    ) -> io::Result<ReqSocket<Stream>>
227    where
228        Stream: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
229    {
230        Ok(ReqSocket {
231            inner: InternalReq::with_options(stream, options).await?,
232            monitor: None,
233        })
234    }
235}
236
237// Generic impl - works with any stream type
238impl<S> ReqSocket<S>
239where
240    S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
241{
242    /// Enable monitoring for this socket.
243    ///
244    /// Returns a receiver for socket lifecycle events.
245    pub fn monitor(&mut self) -> SocketMonitor {
246        let (sender, receiver) = create_monitor();
247        self.monitor = Some(sender);
248        receiver
249    }
250
251    /// Helper to emit monitoring events (if monitoring is enabled).
252    fn emit_event(&self, event: SocketEvent) {
253        if let Some(monitor) = &self.monitor {
254            monocoque_core::monitor::emit(monitor, event);
255        }
256    }
257
258    /// Send a multipart message.
259    ///
260    /// This enforces the REQ state machine - you must call `recv()` before
261    /// calling `send()` again.
262    ///
263    /// # Arguments
264    ///
265    /// * `msg` - Vector of message frames (parts)
266    ///
267    /// # Errors
268    ///
269    /// Returns an error if:
270    /// - Called while awaiting a reply (must call `recv()` first)
271    /// - The underlying connection is closed
272    ///
273    /// # Example
274    ///
275    /// ```rust,no_run
276    /// use monocoque::zmq::ReqSocket;
277    /// use bytes::Bytes;
278    ///
279    /// # async fn example(socket: &mut ReqSocket) -> std::io::Result<()> {
280    /// // Send single-part message
281    /// socket.send(vec![Bytes::from("Hello")]).await?;
282    ///
283    /// // Send multi-part message
284    /// socket.send(vec![
285    ///     Bytes::from("Part 1"),
286    ///     Bytes::from("Part 2"),
287    /// ]).await?;
288    /// # Ok(())
289    /// # }
290    /// ```
291    pub async fn send(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
292        channel_to_io_error(self.inner.send(msg).await)
293    }
294
295    /// Send a single-frame request without allocating a `Vec`.
296    ///
297    /// Allocation-free counterpart to [`send`](Self::send) for the common
298    /// one-frame request: the wire envelope (optional correlation ID, empty
299    /// delimiter, body) is built on the stack. Paired with
300    /// [`recv_into`](Self::recv_into), a round trip performs no per-message heap
301    /// allocation.
302    ///
303    /// # Errors
304    ///
305    /// Returns an error if called while awaiting a reply (outside relaxed mode)
306    /// or if the underlying send fails.
307    ///
308    /// # Example
309    ///
310    /// ```rust,no_run
311    /// use monocoque::zmq::ReqSocket;
312    /// use bytes::Bytes;
313    ///
314    /// # async fn example(socket: &mut ReqSocket) -> std::io::Result<()> {
315    /// let mut reply: Vec<Bytes> = Vec::new();
316    /// socket.send_one(Bytes::from_static(b"ping")).await?;
317    /// socket.recv_into(&mut reply).await?;
318    /// # Ok(())
319    /// # }
320    /// ```
321    pub async fn send_one(&mut self, frame: Bytes) -> io::Result<()> {
322        channel_to_io_error(self.inner.send_one(frame).await)
323    }
324
325    /// Get the socket type.
326    ///
327    /// # ZeroMQ Compatibility
328    ///
329    /// Corresponds to `ZMQ_TYPE` (16) option.
330    #[inline]
331    pub const fn socket_type() -> SocketType {
332        SocketType::Req
333    }
334
335    /// Get the endpoint this socket is connected/bound to, if available.
336    ///
337    /// Returns `None` if the socket was created from a raw stream.
338    ///
339    /// # ZeroMQ Compatibility
340    ///
341    /// Corresponds to `ZMQ_LAST_ENDPOINT` (32) option.
342    #[inline]
343    pub fn last_endpoint(&self) -> Option<&monocoque_core::endpoint::Endpoint> {
344        self.inner.last_endpoint()
345    }
346
347    /// Check if the last received message has more frames coming.
348    ///
349    /// Returns `true` if there are more frames in the current multipart message.
350    ///
351    /// # ZeroMQ Compatibility
352    ///
353    /// Corresponds to `ZMQ_RCVMORE` (13) option.
354    #[inline]
355    pub fn has_more(&self) -> bool {
356        self.inner.has_more()
357    }
358
359    /// Get the event state of the socket.
360    ///
361    /// Returns a bitmask indicating ready-to-receive and ready-to-send states.
362    ///
363    /// # Returns
364    ///
365    /// - `1` (POLLIN) - Socket is ready to receive
366    /// - `2` (POLLOUT) - Socket is ready to send
367    /// - `3` (POLLIN | POLLOUT) - Socket is ready for both
368    ///
369    /// # ZeroMQ Compatibility
370    ///
371    /// Corresponds to `ZMQ_EVENTS` (15) option.
372    #[inline]
373    pub fn events(&self) -> u32 {
374        self.inner.events()
375    }
376
377    /// Receive a multipart message.
378    ///
379    /// This blocks until a reply is received. You must call this after `send()`
380    /// before calling `send()` again.
381    ///
382    /// # Returns
383    ///
384    /// - `Ok(Some(msg))` - Received a multipart message
385    /// - `Ok(None)` - Connection closed gracefully (peer disconnected)
386    /// - `Err(e)` - Network or I/O error
387    ///
388    /// # Example
389    ///
390    /// ```rust,no_run
391    /// use monocoque::zmq::ReqSocket;
392    ///
393    /// # async fn example(socket: &mut ReqSocket) -> std::io::Result<()> {
394    /// if let Some(reply) = socket.recv().await? {
395    ///     for (i, frame) in reply.iter().enumerate() {
396    ///         println!("Frame {}: {:?}", i, frame);
397    ///     }
398    /// }
399    /// # Ok(())
400    /// # }
401    /// ```
402    pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
403        self.inner.recv().await
404    }
405
406    /// Receive a reply into a caller-provided buffer, reusing its allocation.
407    ///
408    /// Allocation-free counterpart to [`recv`](Self::recv) with the envelope
409    /// stripped. Returns `Ok(true)` on a reply, `Ok(false)` on EOF.
410    pub async fn recv_into(&mut self, out: &mut Vec<Bytes>) -> io::Result<bool> {
411        self.inner.recv_into(out).await
412    }
413
414    /// Get a reference to the socket options.
415    ///
416    /// # Example
417    ///
418    /// ```rust,no_run
419    /// use monocoque::zmq::ReqSocket;
420    ///
421    /// # async fn example(socket: &ReqSocket) {
422    /// let timeout = socket.options().recv_timeout;
423    /// println!("Receive timeout: {:?}", timeout);
424    /// # }
425    /// ```
426    pub const fn options(&self) -> &monocoque_core::options::SocketOptions {
427        self.inner.options()
428    }
429
430    /// Get a mutable reference to the socket options.
431    ///
432    /// # Example
433    ///
434    /// ```rust,no_run
435    /// use monocoque::zmq::ReqSocket;
436    /// use std::time::Duration;
437    ///
438    /// # async fn example(socket: &mut ReqSocket) {
439    /// socket.options_mut().recv_timeout = Some(Duration::from_secs(30));
440    /// # }
441    /// ```
442    pub fn options_mut(&mut self) -> &mut monocoque_core::options::SocketOptions {
443        self.inner.options_mut()
444    }
445
446    /// Set the socket options.
447    ///
448    /// # Example
449    ///
450    /// ```rust,no_run
451    /// use monocoque::zmq::ReqSocket;
452    /// use monocoque::SocketOptions;
453    /// use std::time::Duration;
454    ///
455    /// # async fn example(socket: &mut ReqSocket) {
456    /// let options = SocketOptions::default()
457    ///     .with_send_timeout(Duration::from_secs(5))
458    ///     .with_recv_timeout(Duration::from_secs(10));
459    ///
460    /// socket.set_options(options);
461    /// # }
462    /// ```
463    pub fn set_options(&mut self, options: monocoque_core::options::SocketOptions) {
464        self.inner.set_options(options);
465    }
466}
467
468// Unix-specific impl for IPC support
469#[cfg(unix)]
470impl ReqSocket<monocoque_core::rt::UnixStream> {
471    /// Create a REQ socket from an existing Unix domain socket stream (IPC).
472    pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
473        Ok(Self {
474            inner: InternalReq::new(stream).await?,
475            monitor: None,
476        })
477    }
478
479    /// Create a REQ socket from an existing Unix stream with custom options.
480    ///
481    /// This method provides full control over socket behavior through SocketOptions.
482    pub async fn from_unix_stream_with_options(
483        stream: monocoque_core::rt::UnixStream,
484        options: monocoque_core::options::SocketOptions,
485    ) -> io::Result<Self> {
486        Ok(Self {
487            inner: InternalReq::with_options(stream, options).await?,
488            monitor: None,
489        })
490    }
491}