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 /// Get the socket type.
296 ///
297 /// # ZeroMQ Compatibility
298 ///
299 /// Corresponds to `ZMQ_TYPE` (16) option.
300 #[inline]
301 pub const fn socket_type() -> SocketType {
302 SocketType::Req
303 }
304
305 /// Get the endpoint this socket is connected/bound to, if available.
306 ///
307 /// Returns `None` if the socket was created from a raw stream.
308 ///
309 /// # ZeroMQ Compatibility
310 ///
311 /// Corresponds to `ZMQ_LAST_ENDPOINT` (32) option.
312 #[inline]
313 pub fn last_endpoint(&self) -> Option<&monocoque_core::endpoint::Endpoint> {
314 self.inner.last_endpoint()
315 }
316
317 /// Check if the last received message has more frames coming.
318 ///
319 /// Returns `true` if there are more frames in the current multipart message.
320 ///
321 /// # ZeroMQ Compatibility
322 ///
323 /// Corresponds to `ZMQ_RCVMORE` (13) option.
324 #[inline]
325 pub fn has_more(&self) -> bool {
326 self.inner.has_more()
327 }
328
329 /// Get the event state of the socket.
330 ///
331 /// Returns a bitmask indicating ready-to-receive and ready-to-send states.
332 ///
333 /// # Returns
334 ///
335 /// - `1` (POLLIN) - Socket is ready to receive
336 /// - `2` (POLLOUT) - Socket is ready to send
337 /// - `3` (POLLIN | POLLOUT) - Socket is ready for both
338 ///
339 /// # ZeroMQ Compatibility
340 ///
341 /// Corresponds to `ZMQ_EVENTS` (15) option.
342 #[inline]
343 pub fn events(&self) -> u32 {
344 self.inner.events()
345 }
346
347 /// Receive a multipart message.
348 ///
349 /// This blocks until a reply is received. You must call this after `send()`
350 /// before calling `send()` again.
351 ///
352 /// # Returns
353 ///
354 /// - `Ok(Some(msg))` - Received a multipart message
355 /// - `Ok(None)` - Connection closed gracefully (peer disconnected)
356 /// - `Err(e)` - Network or I/O error
357 ///
358 /// # Example
359 ///
360 /// ```rust,no_run
361 /// use monocoque::zmq::ReqSocket;
362 ///
363 /// # async fn example(socket: &mut ReqSocket) -> std::io::Result<()> {
364 /// if let Some(reply) = socket.recv().await? {
365 /// for (i, frame) in reply.iter().enumerate() {
366 /// println!("Frame {}: {:?}", i, frame);
367 /// }
368 /// }
369 /// # Ok(())
370 /// # }
371 /// ```
372 pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
373 self.inner.recv().await
374 }
375
376 /// Get a reference to the socket options.
377 ///
378 /// # Example
379 ///
380 /// ```rust,no_run
381 /// use monocoque::zmq::ReqSocket;
382 ///
383 /// # async fn example(socket: &ReqSocket) {
384 /// let timeout = socket.options().recv_timeout;
385 /// println!("Receive timeout: {:?}", timeout);
386 /// # }
387 /// ```
388 pub const fn options(&self) -> &monocoque_core::options::SocketOptions {
389 self.inner.options()
390 }
391
392 /// Get a mutable reference to the socket options.
393 ///
394 /// # Example
395 ///
396 /// ```rust,no_run
397 /// use monocoque::zmq::ReqSocket;
398 /// use std::time::Duration;
399 ///
400 /// # async fn example(socket: &mut ReqSocket) {
401 /// socket.options_mut().recv_timeout = Some(Duration::from_secs(30));
402 /// # }
403 /// ```
404 pub fn options_mut(&mut self) -> &mut monocoque_core::options::SocketOptions {
405 self.inner.options_mut()
406 }
407
408 /// Set the socket options.
409 ///
410 /// # Example
411 ///
412 /// ```rust,no_run
413 /// use monocoque::zmq::ReqSocket;
414 /// use monocoque::SocketOptions;
415 /// use std::time::Duration;
416 ///
417 /// # async fn example(socket: &mut ReqSocket) {
418 /// let options = SocketOptions::default()
419 /// .with_send_timeout(Duration::from_secs(5))
420 /// .with_recv_timeout(Duration::from_secs(10));
421 ///
422 /// socket.set_options(options);
423 /// # }
424 /// ```
425 pub fn set_options(&mut self, options: monocoque_core::options::SocketOptions) {
426 self.inner.set_options(options);
427 }
428}
429
430// Unix-specific impl for IPC support
431#[cfg(unix)]
432impl ReqSocket<monocoque_core::rt::UnixStream> {
433 /// Create a REQ socket from an existing Unix domain socket stream (IPC).
434 pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
435 Ok(Self {
436 inner: InternalReq::new(stream).await?,
437 monitor: None,
438 })
439 }
440
441 /// Create a REQ socket from an existing Unix stream with custom options.
442 ///
443 /// This method provides full control over socket behavior through SocketOptions.
444 pub async fn from_unix_stream_with_options(
445 stream: monocoque_core::rt::UnixStream,
446 options: monocoque_core::options::SocketOptions,
447 ) -> io::Result<Self> {
448 Ok(Self {
449 inner: InternalReq::with_options(stream, options).await?,
450 monitor: None,
451 })
452 }
453}