omq 0.10.0 → 0.11.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +63 -1
- data/README.md +28 -3
- data/lib/omq/channel.rb +2 -2
- data/lib/omq/client_server.rb +4 -4
- data/lib/omq/engine.rb +73 -12
- data/lib/omq/options.rb +3 -1
- data/lib/omq/pair.rb +2 -2
- data/lib/omq/peer.rb +2 -2
- data/lib/omq/pub_sub.rb +12 -12
- data/lib/omq/push_pull.rb +4 -4
- data/lib/omq/queue_interface.rb +73 -0
- data/lib/omq/radio_dish.rb +4 -4
- data/lib/omq/readable.rb +2 -0
- data/lib/omq/req_rep.rb +4 -4
- data/lib/omq/router_dealer.rb +4 -4
- data/lib/omq/routing/fan_out.rb +11 -2
- data/lib/omq/routing/radio.rb +13 -3
- data/lib/omq/scatter_gather.rb +4 -4
- data/lib/omq/socket.rb +9 -3
- data/lib/omq/transport/tls.rb +146 -0
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +1 -0
- data/lib/omq.rb +4 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b736b6b715f5c4f330d21be278450600b2642239b8bea9e85957f516e89211ed
|
|
4
|
+
data.tar.gz: 34c057a32055e7333e4299e85c0fee9e798b43ea34d021f237ba6778338b65f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e95a6df6d4eb56ac1fc0586f61cfdb77d0afe9de620d3e540ffbae1256c2fe6526556485215eda3de20c8f34a332f51a68e5bb075087665049c0219bb07f6960
|
|
7
|
+
data.tar.gz: 8b5c8d38b7523e9c6ab30e17c21b0cf4adb2086e1bdde90298f62e42f4b8f2336cdd95c47293c3fb24fa41f376aed8a2ec6d1121fc56d41aa83d4afdc289d707
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`backend:` kwarg** — all socket types accept `backend: :ffi` to use
|
|
8
|
+
the libzmq FFI backend (via the [omq-ffi](https://github.com/paddor/omq-ffi)
|
|
9
|
+
gem). Default is `:ruby` (pure Ruby ZMTP). Enables interop testing and
|
|
10
|
+
access to libzmq-specific features without changing the socket API.
|
|
11
|
+
- **TLS transport (`tls+tcp://`)** — TLS v1.3 on top of TCP using Ruby's
|
|
12
|
+
stdlib `openssl`. Set `socket.tls_context` to an `OpenSSL::SSL::SSLContext`
|
|
13
|
+
before bind/connect. Per-socket (not per-endpoint), frozen on first use.
|
|
14
|
+
SNI set automatically from the endpoint hostname. Bad TLS handshakes are
|
|
15
|
+
dropped without killing the accept loop. `OpenSSL::SSL::SSLError` added
|
|
16
|
+
to `CONNECTION_LOST` for automatic reconnection on TLS failures.
|
|
17
|
+
Accompanied by a draft RFC (`rfc/zmtp-tls.md`) defining the transport
|
|
18
|
+
mapping for ZMTP 3.1 over TLS.
|
|
19
|
+
- **PUB/RADIO fan-out pre-encoding** — ZMTP frames are encoded once per
|
|
20
|
+
message and written as raw wire bytes to all non-CURVE subscribers.
|
|
21
|
+
Eliminates redundant `Frame.new` + `#to_wire` calls during fan-out.
|
|
22
|
+
CURVE connections (which encrypt at the ZMTP level) still encode
|
|
23
|
+
per-connection. TLS, NULL, and PLAIN all benefit since TLS encrypts
|
|
24
|
+
below ZMTP. Requires protocol-zmtp `Frame.encode_message` and
|
|
25
|
+
`Connection#write_wire`.
|
|
26
|
+
- **CURVE benchmarks** — all per-pattern benchmarks now include CURVE
|
|
27
|
+
(via rbnacl) alongside inproc, ipc, tcp, and tls transports.
|
|
28
|
+
- **Engine `connection_wrapper` hook** — optional proc on Engine that wraps
|
|
29
|
+
new connections (both inproc and tcp/ipc) at creation time. Used by the
|
|
30
|
+
omq-ractor gem for per-connection serialization (Marshal for tcp/ipc,
|
|
31
|
+
`Ractor.make_shareable` for inproc).
|
|
32
|
+
- **Queue-style interface** — readable sockets gain `#dequeue(timeout:)`,
|
|
33
|
+
`#pop`, `#wait`, and `#each`; writable sockets gain `#enqueue` and
|
|
34
|
+
`#push`. Inspired by `Async::Queue`. `#wait` blocks indefinitely
|
|
35
|
+
(ignores `read_timeout`); `#each` returns gracefully on timeout.
|
|
36
|
+
- **Recv pump fairness** — each connection yields to the fiber scheduler
|
|
37
|
+
after 64 messages or 1 MB (whichever comes first). Prevents a fast or
|
|
38
|
+
large-message connection from starving slower peers when the consumer
|
|
39
|
+
keeps up. Byte counting gracefully handles non-string messages (e.g.
|
|
40
|
+
deserialized objects from connection wrappers).
|
|
41
|
+
- **Per-pattern benchmark suite** — `bench/{push_pull,req_rep,router_dealer,dealer_dealer,pub_sub,pair}/omq.rb`
|
|
42
|
+
with shared helpers (`bench_helper.rb`) and UnicodePlot braille line
|
|
43
|
+
charts (`plot.rb`). Each benchmark measures throughput (msg/s) and
|
|
44
|
+
bandwidth (MB/s) across transports (inproc, ipc, tcp, tls, curve),
|
|
45
|
+
message sizes (64 B–64 KB), and peer counts (1, 3). Plots are written to per-directory
|
|
46
|
+
`README.md` files for easy diffing across versions.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **SUB/XSUB `prefix:` kwarg renamed to `subscribe:`** — aligns with
|
|
51
|
+
ZeroMQ conventions. `subscribe: nil` (no subscription) remains the
|
|
52
|
+
default; pass `subscribe: ''` to subscribe to everything, or
|
|
53
|
+
`subscribe: 'topic.'` for a prefix filter.
|
|
54
|
+
- **Scenario benchmarks moved to `bench/scenarios/`** — broker,
|
|
55
|
+
draft_types, flush_batching, hwm_backpressure, large_messages,
|
|
56
|
+
multiframe, pubsub_fanout, ractors_vs_async, ractors_vs_fork,
|
|
57
|
+
reconnect_storm, and reqrep_throughput moved from `bench/` top level.
|
|
58
|
+
|
|
59
|
+
### Removed
|
|
60
|
+
|
|
61
|
+
- **Old flat benchmarks** — `bench/throughput.rb`, `bench/latency.rb`,
|
|
62
|
+
`bench/pipeline_mbps.rb`, `bench/run_all.sh` replaced by per-pattern
|
|
63
|
+
benchmarks.
|
|
64
|
+
- **`bench/cli/`** — CLI-specific benchmarks (fib pipeline, latency,
|
|
65
|
+
throughput shell scripts) moved to the omq-cli repository.
|
|
4
66
|
|
|
5
67
|
## 0.10.0 — 2026-04-01
|
|
6
68
|
|
data/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
OMQ builds ZeroMQ socket patterns on top of [protocol-zmtp](https://github.com/paddor/protocol-zmtp) (a pure Ruby [ZMTP 3.1](https://rfc.zeromq.org/spec/23/) codec) using [Async](https://github.com/socketry/async) fibers. It speaks native ZeroMQ on the wire and interoperates with libzmq, pyzmq, CZMQ, and everything else in the ZMQ ecosystem.
|
|
11
11
|
|
|
12
|
-
> **
|
|
12
|
+
> **980k msg/s** inproc | **38k msg/s** ipc | **31k msg/s** tcp
|
|
13
13
|
>
|
|
14
|
-
> **
|
|
14
|
+
> **10 µs** inproc latency | **71 µs** ipc | **82 µs** tcp
|
|
15
15
|
>
|
|
16
16
|
> Ruby 4.0 + YJIT on a Linux VM — see [`bench/`](bench/) for full results
|
|
17
17
|
|
|
@@ -30,7 +30,7 @@ See [GETTING_STARTED.md](GETTING_STARTED.md) for a ~30 min walkthrough of all ma
|
|
|
30
30
|
## Highlights
|
|
31
31
|
|
|
32
32
|
- **Zero dependencies on C** — no extensions, no FFI, no libzmq. `gem install` just works everywhere
|
|
33
|
-
- **Fast** — YJIT-optimized hot paths, batched sends,
|
|
33
|
+
- **Fast** — YJIT-optimized hot paths, batched sends, recv prefetching, direct-pipe inproc bypass. 980k msg/s inproc, 10 µs latency
|
|
34
34
|
- **[`omq` CLI](https://github.com/paddor/omq-cli)** — `gem install omq-cli` for a command-line tool with Ruby eval, Ractor parallelism, and script handlers
|
|
35
35
|
- **Every socket pattern** — req/rep, pub/sub, push/pull, dealer/router, xpub/xsub, pair, and all draft types
|
|
36
36
|
- **Every transport** — tcp, ipc (Unix domain sockets), inproc (in-process queues)
|
|
@@ -125,6 +125,26 @@ pull.close
|
|
|
125
125
|
|
|
126
126
|
The IO thread runs all pumps, reconnection, and heartbeating in the background. When you're inside an `Async` block, OMQ uses the existing reactor instead.
|
|
127
127
|
|
|
128
|
+
### Queue Interface
|
|
129
|
+
|
|
130
|
+
All sockets expose an `Async::Queue`-inspired interface:
|
|
131
|
+
|
|
132
|
+
| Async::Queue | OMQ Socket | Notes |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `enqueue(item)` / `push(item)` | `enqueue(msg)` / `push(msg)` | Also: `send(msg)`, `<< msg` |
|
|
135
|
+
| `dequeue(timeout:)` / `pop(timeout:)` | `dequeue(timeout:)` / `pop(timeout:)` | Defaults to socket's `read_timeout` |
|
|
136
|
+
| `wait` | `wait` | Blocks indefinitely (ignores `read_timeout`) |
|
|
137
|
+
| `each` | `each` | Yields messages; returns on close or timeout |
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
pull = OMQ::PULL.bind('inproc://work')
|
|
141
|
+
|
|
142
|
+
# iterate messages like a queue
|
|
143
|
+
pull.each do |msg|
|
|
144
|
+
puts msg.first
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
128
148
|
## Socket Types
|
|
129
149
|
|
|
130
150
|
All sockets are thread-safe. Default HWM is 1000 messages per socket. Classes live under `OMQ::` (alias: `ØMQ`).
|
|
@@ -163,6 +183,11 @@ echo "hello" | omq req -c tcp://localhost:5555
|
|
|
163
183
|
|
|
164
184
|
See the [omq-cli README](https://github.com/paddor/omq-cli) for full documentation.
|
|
165
185
|
|
|
186
|
+
## Companion Gems
|
|
187
|
+
|
|
188
|
+
- **[omq-ffi](https://github.com/paddor/omq-ffi)** — libzmq FFI backend. Same OMQ socket API, but backed by libzmq instead of the pure Ruby ZMTP stack. Useful for interop testing and when you need libzmq-specific features. Requires libzmq installed.
|
|
189
|
+
- **[omq-ractor](https://github.com/paddor/omq-ractor)** — bridge OMQ sockets into Ruby Ractors for true parallel processing across cores. I/O stays on the main Ractor, worker Ractors do pure computation.
|
|
190
|
+
|
|
166
191
|
## Development
|
|
167
192
|
|
|
168
193
|
```sh
|
data/lib/omq/channel.rb
CHANGED
|
@@ -6,8 +6,8 @@ module OMQ
|
|
|
6
6
|
include Writable
|
|
7
7
|
include SingleFrame
|
|
8
8
|
|
|
9
|
-
def initialize(endpoints = nil, linger: 0)
|
|
10
|
-
_init_engine(:CHANNEL, linger: linger)
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:CHANNEL, linger: linger, backend: backend)
|
|
11
11
|
_attach(endpoints, default: :connect)
|
|
12
12
|
end
|
|
13
13
|
end
|
data/lib/omq/client_server.rb
CHANGED
|
@@ -6,8 +6,8 @@ module OMQ
|
|
|
6
6
|
include Writable
|
|
7
7
|
include SingleFrame
|
|
8
8
|
|
|
9
|
-
def initialize(endpoints = nil, linger: 0)
|
|
10
|
-
_init_engine(:CLIENT, linger: linger)
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:CLIENT, linger: linger, backend: backend)
|
|
11
11
|
_attach(endpoints, default: :connect)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -17,8 +17,8 @@ module OMQ
|
|
|
17
17
|
include Writable
|
|
18
18
|
include SingleFrame
|
|
19
19
|
|
|
20
|
-
def initialize(endpoints = nil, linger: 0)
|
|
21
|
-
_init_engine(:SERVER, linger: linger)
|
|
20
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
21
|
+
_init_engine(:SERVER, linger: linger, backend: backend)
|
|
22
22
|
_attach(endpoints, default: :bind)
|
|
23
23
|
end
|
|
24
24
|
|
data/lib/omq/engine.rb
CHANGED
|
@@ -64,6 +64,11 @@ module OMQ
|
|
|
64
64
|
|
|
65
65
|
attr_writer :reconnect_enabled
|
|
66
66
|
|
|
67
|
+
# Optional proc that wraps new connections (e.g. for serialization).
|
|
68
|
+
# Called with the raw connection; must return the (possibly wrapped) connection.
|
|
69
|
+
#
|
|
70
|
+
attr_accessor :connection_wrapper
|
|
71
|
+
|
|
67
72
|
|
|
68
73
|
# Spawns an inproc reconnect retry task under @parent_task.
|
|
69
74
|
#
|
|
@@ -184,6 +189,7 @@ module OMQ
|
|
|
184
189
|
# @return [void]
|
|
185
190
|
#
|
|
186
191
|
def connection_ready(pipe, endpoint: nil)
|
|
192
|
+
pipe = @connection_wrapper.call(pipe) if @connection_wrapper
|
|
187
193
|
@connections << pipe
|
|
188
194
|
@connection_endpoints[pipe] = endpoint if endpoint
|
|
189
195
|
@routing.connection_added(pipe)
|
|
@@ -262,6 +268,13 @@ module OMQ
|
|
|
262
268
|
# @yield [msg] optional per-message transform
|
|
263
269
|
# @return [#stop, nil] pump task handle, or nil for DirectPipe bypass
|
|
264
270
|
#
|
|
271
|
+
# Fairness limits for the recv pump. Yield to the scheduler
|
|
272
|
+
# after reading this many messages or bytes from one connection,
|
|
273
|
+
# whichever comes first. Prevents a fast or large-message
|
|
274
|
+
# connection from starving slower peers.
|
|
275
|
+
RECV_FAIRNESS_MESSAGES = 64
|
|
276
|
+
RECV_FAIRNESS_BYTES = 1 << 20 # 1 MB
|
|
277
|
+
|
|
265
278
|
def start_recv_pump(conn, recv_queue, &transform)
|
|
266
279
|
if conn.is_a?(Transport::Inproc::DirectPipe) && conn.peer
|
|
267
280
|
conn.peer.direct_recv_queue = recv_queue
|
|
@@ -270,11 +283,18 @@ module OMQ
|
|
|
270
283
|
end
|
|
271
284
|
|
|
272
285
|
if transform
|
|
273
|
-
@parent_task.async(transient: true, annotation: "recv pump") do
|
|
286
|
+
@parent_task.async(transient: true, annotation: "recv pump") do |task|
|
|
274
287
|
loop do
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
288
|
+
count = 0
|
|
289
|
+
bytes = 0
|
|
290
|
+
while count < RECV_FAIRNESS_MESSAGES && bytes < RECV_FAIRNESS_BYTES
|
|
291
|
+
msg = conn.receive_message
|
|
292
|
+
msg = transform.call(msg).freeze
|
|
293
|
+
recv_queue.enqueue(msg)
|
|
294
|
+
count += 1
|
|
295
|
+
bytes += msg.is_a?(Array) && msg.first.is_a?(String) ? msg.sum(&:bytesize) : 0
|
|
296
|
+
end
|
|
297
|
+
task.yield
|
|
278
298
|
end
|
|
279
299
|
rescue Async::Stop
|
|
280
300
|
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
@@ -283,9 +303,17 @@ module OMQ
|
|
|
283
303
|
signal_fatal_error(error)
|
|
284
304
|
end
|
|
285
305
|
else
|
|
286
|
-
@parent_task.async(transient: true, annotation: "recv pump") do
|
|
306
|
+
@parent_task.async(transient: true, annotation: "recv pump") do |task|
|
|
287
307
|
loop do
|
|
288
|
-
|
|
308
|
+
count = 0
|
|
309
|
+
bytes = 0
|
|
310
|
+
while count < RECV_FAIRNESS_MESSAGES && bytes < RECV_FAIRNESS_BYTES
|
|
311
|
+
msg = conn.receive_message
|
|
312
|
+
recv_queue.enqueue(msg)
|
|
313
|
+
count += 1
|
|
314
|
+
bytes += msg.is_a?(Array) && msg.first.is_a?(String) ? msg.sum(&:bytesize) : 0
|
|
315
|
+
end
|
|
316
|
+
task.yield
|
|
289
317
|
end
|
|
290
318
|
rescue Async::Stop
|
|
291
319
|
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
@@ -485,6 +513,7 @@ module OMQ
|
|
|
485
513
|
)
|
|
486
514
|
conn.handshake!
|
|
487
515
|
start_heartbeat(conn)
|
|
516
|
+
conn = @connection_wrapper.call(conn) if @connection_wrapper
|
|
488
517
|
@connections << conn
|
|
489
518
|
@connection_endpoints[conn] = endpoint if endpoint
|
|
490
519
|
@connection_promises[conn] = done if done
|
|
@@ -572,24 +601,31 @@ module OMQ
|
|
|
572
601
|
# resolution failures during reconnect are retried with backoff.
|
|
573
602
|
#
|
|
574
603
|
def validate_endpoint!(endpoint)
|
|
575
|
-
|
|
576
|
-
|
|
604
|
+
case endpoint
|
|
605
|
+
when /\Atcp:\/\//
|
|
606
|
+
host = URI.parse(endpoint.sub("tcp://", "http://")).hostname
|
|
607
|
+
when /\Atls\+tcp:\/\//
|
|
608
|
+
host = URI.parse("http://#{endpoint.delete_prefix("tls+tcp://")}").hostname
|
|
609
|
+
else
|
|
610
|
+
return
|
|
611
|
+
end
|
|
577
612
|
Addrinfo.getaddrinfo(host, nil, nil, :STREAM) if host
|
|
578
613
|
end
|
|
579
614
|
|
|
580
615
|
|
|
581
616
|
def transport_for(endpoint)
|
|
582
617
|
case endpoint
|
|
583
|
-
when /\
|
|
584
|
-
when /\
|
|
585
|
-
when /\
|
|
618
|
+
when /\Atls\+tcp:\/\// then Transport::TLS
|
|
619
|
+
when /\Atcp:\/\// then Transport::TCP
|
|
620
|
+
when /\Aipc:\/\// then Transport::IPC
|
|
621
|
+
when /\Ainproc:\/\// then Transport::Inproc
|
|
586
622
|
else raise ArgumentError, "unsupported transport: #{endpoint}"
|
|
587
623
|
end
|
|
588
624
|
end
|
|
589
625
|
|
|
590
626
|
|
|
591
627
|
def extract_tcp_port(endpoint)
|
|
592
|
-
return nil unless endpoint&.start_with?("tcp://")
|
|
628
|
+
return nil unless endpoint&.start_with?("tcp://") || endpoint&.start_with?("tls+tcp://")
|
|
593
629
|
port = endpoint.split(":").last.to_i
|
|
594
630
|
port.positive? ? port : nil
|
|
595
631
|
end
|
|
@@ -602,6 +638,31 @@ module OMQ
|
|
|
602
638
|
#
|
|
603
639
|
def start_accept_loops(listener)
|
|
604
640
|
case listener
|
|
641
|
+
when Transport::TLS::Listener
|
|
642
|
+
tasks = listener.servers.map do |server|
|
|
643
|
+
@parent_task.async(transient: true, annotation: "tls accept #{listener.endpoint}") do
|
|
644
|
+
loop do
|
|
645
|
+
client = server.accept
|
|
646
|
+
Async::Task.current.defer_stop do
|
|
647
|
+
ssl = OpenSSL::SSL::SSLSocket.new(client, listener.ssl_context)
|
|
648
|
+
ssl.sync_close = true
|
|
649
|
+
ssl.accept
|
|
650
|
+
handle_accepted(IO::Stream::Buffered.wrap(ssl), endpoint: listener.endpoint)
|
|
651
|
+
rescue OpenSSL::SSL::SSLError
|
|
652
|
+
# Bad certificate, protocol mismatch, etc. — drop this
|
|
653
|
+
# connection but keep the accept loop running.
|
|
654
|
+
ssl&.close rescue nil
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
rescue Async::Stop
|
|
658
|
+
rescue IOError
|
|
659
|
+
# server closed
|
|
660
|
+
ensure
|
|
661
|
+
server.close rescue nil
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
listener.accept_tasks = tasks
|
|
665
|
+
|
|
605
666
|
when Transport::TCP::Listener
|
|
606
667
|
tasks = listener.servers.map do |server|
|
|
607
668
|
@parent_task.async(transient: true, annotation: "tcp accept #{listener.endpoint}") do
|
data/lib/omq/options.rb
CHANGED
|
@@ -26,6 +26,7 @@ module OMQ
|
|
|
26
26
|
@max_message_size = nil # bytes, nil = unlimited
|
|
27
27
|
@conflate = false
|
|
28
28
|
@mechanism = Protocol::ZMTP::Mechanism::Null.new
|
|
29
|
+
@tls_context = nil # OpenSSL::SSL::SSLContext for tls+tcp://
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
attr_accessor :send_hwm, :recv_hwm,
|
|
@@ -35,7 +36,8 @@ module OMQ
|
|
|
35
36
|
:reconnect_interval,
|
|
36
37
|
:heartbeat_interval, :heartbeat_ttl, :heartbeat_timeout,
|
|
37
38
|
:max_message_size,
|
|
38
|
-
:mechanism
|
|
39
|
+
:mechanism,
|
|
40
|
+
:tls_context
|
|
39
41
|
|
|
40
42
|
alias_method :router_mandatory?, :router_mandatory
|
|
41
43
|
alias_method :recv_timeout, :read_timeout
|
data/lib/omq/pair.rb
CHANGED
|
@@ -5,8 +5,8 @@ module OMQ
|
|
|
5
5
|
include Readable
|
|
6
6
|
include Writable
|
|
7
7
|
|
|
8
|
-
def initialize(endpoints = nil, linger: 0)
|
|
9
|
-
_init_engine(:PAIR, linger: linger)
|
|
8
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
9
|
+
_init_engine(:PAIR, linger: linger, backend: backend)
|
|
10
10
|
_attach(endpoints, default: :connect)
|
|
11
11
|
end
|
|
12
12
|
end
|
data/lib/omq/peer.rb
CHANGED
|
@@ -6,8 +6,8 @@ module OMQ
|
|
|
6
6
|
include Writable
|
|
7
7
|
include SingleFrame
|
|
8
8
|
|
|
9
|
-
def initialize(endpoints = nil, linger: 0)
|
|
10
|
-
_init_engine(:PEER, linger: linger)
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:PEER, linger: linger, backend: backend)
|
|
11
11
|
_attach(endpoints, default: :connect)
|
|
12
12
|
end
|
|
13
13
|
|
data/lib/omq/pub_sub.rb
CHANGED
|
@@ -4,8 +4,8 @@ module OMQ
|
|
|
4
4
|
class PUB < Socket
|
|
5
5
|
include Writable
|
|
6
6
|
|
|
7
|
-
def initialize(endpoints = nil, linger: 0, conflate: false)
|
|
8
|
-
_init_engine(:PUB, linger: linger, conflate: conflate)
|
|
7
|
+
def initialize(endpoints = nil, linger: 0, conflate: false, backend: nil)
|
|
8
|
+
_init_engine(:PUB, linger: linger, conflate: conflate, backend: backend)
|
|
9
9
|
_attach(endpoints, default: :bind)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -21,13 +21,13 @@ module OMQ
|
|
|
21
21
|
|
|
22
22
|
# @param endpoints [String, nil]
|
|
23
23
|
# @param linger [Integer]
|
|
24
|
-
# @param
|
|
24
|
+
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
25
25
|
# means no subscription — call {#subscribe} explicitly.
|
|
26
26
|
#
|
|
27
|
-
def initialize(endpoints = nil, linger: 0,
|
|
28
|
-
_init_engine(:SUB, linger: linger)
|
|
27
|
+
def initialize(endpoints = nil, linger: 0, subscribe: nil, backend: nil)
|
|
28
|
+
_init_engine(:SUB, linger: linger, backend: backend)
|
|
29
29
|
_attach(endpoints, default: :connect)
|
|
30
|
-
subscribe(
|
|
30
|
+
self.subscribe(subscribe) unless subscribe.nil?
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Subscribes to a topic prefix.
|
|
@@ -53,8 +53,8 @@ module OMQ
|
|
|
53
53
|
include Readable
|
|
54
54
|
include Writable
|
|
55
55
|
|
|
56
|
-
def initialize(endpoints = nil, linger: 0)
|
|
57
|
-
_init_engine(:XPUB, linger: linger)
|
|
56
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
57
|
+
_init_engine(:XPUB, linger: linger, backend: backend)
|
|
58
58
|
_attach(endpoints, default: :bind)
|
|
59
59
|
end
|
|
60
60
|
end
|
|
@@ -65,13 +65,13 @@ module OMQ
|
|
|
65
65
|
|
|
66
66
|
# @param endpoints [String, nil]
|
|
67
67
|
# @param linger [Integer]
|
|
68
|
-
# @param
|
|
68
|
+
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
69
69
|
# means no subscription — send a subscribe frame explicitly.
|
|
70
70
|
#
|
|
71
|
-
def initialize(endpoints = nil, linger: 0,
|
|
72
|
-
_init_engine(:XSUB, linger: linger)
|
|
71
|
+
def initialize(endpoints = nil, linger: 0, subscribe: nil, backend: nil)
|
|
72
|
+
_init_engine(:XSUB, linger: linger, backend: backend)
|
|
73
73
|
_attach(endpoints, default: :connect)
|
|
74
|
-
send("\x01#{
|
|
74
|
+
send("\x01#{subscribe}".b) unless subscribe.nil?
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
end
|
data/lib/omq/push_pull.rb
CHANGED
|
@@ -4,8 +4,8 @@ module OMQ
|
|
|
4
4
|
class PUSH < Socket
|
|
5
5
|
include Writable
|
|
6
6
|
|
|
7
|
-
def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil)
|
|
8
|
-
_init_engine(:PUSH, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout)
|
|
7
|
+
def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil, backend: nil)
|
|
8
|
+
_init_engine(:PUSH, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout, backend: backend)
|
|
9
9
|
_attach(endpoints, default: :connect)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -13,8 +13,8 @@ module OMQ
|
|
|
13
13
|
class PULL < Socket
|
|
14
14
|
include Readable
|
|
15
15
|
|
|
16
|
-
def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil)
|
|
17
|
-
_init_engine(:PULL, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout)
|
|
16
|
+
def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil, backend: nil)
|
|
17
|
+
_init_engine(:PULL, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout, backend: backend)
|
|
18
18
|
_attach(endpoints, default: :bind)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
# Async::Queue-compatible read interface.
|
|
5
|
+
#
|
|
6
|
+
# Automatically included by {Readable}. Provides #dequeue, #pop,
|
|
7
|
+
# #wait, and #each so sockets can be used where an Async::Queue
|
|
8
|
+
# is expected.
|
|
9
|
+
#
|
|
10
|
+
module QueueReadable
|
|
11
|
+
# Dequeues the next message.
|
|
12
|
+
#
|
|
13
|
+
# @param timeout [Numeric, nil] timeout in seconds (overrides
|
|
14
|
+
# the socket's +read_timeout+ for this call)
|
|
15
|
+
# @return [Array<String>] message parts
|
|
16
|
+
# @raise [IO::TimeoutError] if timeout exceeded
|
|
17
|
+
#
|
|
18
|
+
def dequeue(timeout: @options.read_timeout)
|
|
19
|
+
msg = @recv_mutex.synchronize { @recv_buffer.shift }
|
|
20
|
+
return msg if msg
|
|
21
|
+
|
|
22
|
+
batch = Reactor.run { with_timeout(timeout) { @engine.dequeue_recv_batch(Readable::RECV_BATCH_SIZE) } }
|
|
23
|
+
msg = batch.shift
|
|
24
|
+
@recv_mutex.synchronize { @recv_buffer.concat(batch) } unless batch.empty?
|
|
25
|
+
msg
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
alias_method :pop, :dequeue
|
|
29
|
+
|
|
30
|
+
# Waits for the next message indefinitely (ignores read_timeout).
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<String>] message parts
|
|
33
|
+
#
|
|
34
|
+
def wait
|
|
35
|
+
dequeue(timeout: nil)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Yields each received message until the socket is closed or
|
|
39
|
+
# a receive timeout expires.
|
|
40
|
+
#
|
|
41
|
+
# @yield [Array<String>] message parts
|
|
42
|
+
# @return [void]
|
|
43
|
+
#
|
|
44
|
+
def each
|
|
45
|
+
while (msg = receive)
|
|
46
|
+
yield msg
|
|
47
|
+
end
|
|
48
|
+
rescue IO::TimeoutError
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Async::Queue-compatible write interface.
|
|
55
|
+
#
|
|
56
|
+
# Automatically included by {Writable}. Provides #enqueue, #push,
|
|
57
|
+
# and #signal so sockets can be used where an Async::Queue is
|
|
58
|
+
# expected.
|
|
59
|
+
#
|
|
60
|
+
module QueueWritable
|
|
61
|
+
# Enqueues one or more messages for sending.
|
|
62
|
+
#
|
|
63
|
+
# @param messages [String, Array<String>]
|
|
64
|
+
# @return [self]
|
|
65
|
+
#
|
|
66
|
+
def enqueue(*messages)
|
|
67
|
+
messages.each { |msg| send(msg) }
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
alias_method :push, :enqueue
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/omq/radio_dish.rb
CHANGED
|
@@ -4,8 +4,8 @@ module OMQ
|
|
|
4
4
|
class RADIO < Socket
|
|
5
5
|
include Writable
|
|
6
6
|
|
|
7
|
-
def initialize(endpoints = nil, linger: 0, conflate: false)
|
|
8
|
-
_init_engine(:RADIO, linger: linger, conflate: conflate)
|
|
7
|
+
def initialize(endpoints = nil, linger: 0, conflate: false, backend: nil)
|
|
8
|
+
_init_engine(:RADIO, linger: linger, conflate: conflate, backend: backend)
|
|
9
9
|
_attach(endpoints, default: :bind)
|
|
10
10
|
end
|
|
11
11
|
|
|
@@ -47,8 +47,8 @@ module OMQ
|
|
|
47
47
|
class DISH < Socket
|
|
48
48
|
include Readable
|
|
49
49
|
|
|
50
|
-
def initialize(endpoints = nil, linger: 0, group: nil)
|
|
51
|
-
_init_engine(:DISH, linger: linger)
|
|
50
|
+
def initialize(endpoints = nil, linger: 0, group: nil, backend: nil)
|
|
51
|
+
_init_engine(:DISH, linger: linger, backend: backend)
|
|
52
52
|
_attach(endpoints, default: :connect)
|
|
53
53
|
join(group) if group
|
|
54
54
|
end
|
data/lib/omq/readable.rb
CHANGED
data/lib/omq/req_rep.rb
CHANGED
|
@@ -5,8 +5,8 @@ module OMQ
|
|
|
5
5
|
include Readable
|
|
6
6
|
include Writable
|
|
7
7
|
|
|
8
|
-
def initialize(endpoints = nil, linger: 0)
|
|
9
|
-
_init_engine(:REQ, linger: linger)
|
|
8
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
9
|
+
_init_engine(:REQ, linger: linger, backend: backend)
|
|
10
10
|
_attach(endpoints, default: :connect)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -15,8 +15,8 @@ module OMQ
|
|
|
15
15
|
include Readable
|
|
16
16
|
include Writable
|
|
17
17
|
|
|
18
|
-
def initialize(endpoints = nil, linger: 0)
|
|
19
|
-
_init_engine(:REP, linger: linger)
|
|
18
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
19
|
+
_init_engine(:REP, linger: linger, backend: backend)
|
|
20
20
|
_attach(endpoints, default: :bind)
|
|
21
21
|
end
|
|
22
22
|
end
|
data/lib/omq/router_dealer.rb
CHANGED
|
@@ -5,8 +5,8 @@ module OMQ
|
|
|
5
5
|
include Readable
|
|
6
6
|
include Writable
|
|
7
7
|
|
|
8
|
-
def initialize(endpoints = nil, linger: 0)
|
|
9
|
-
_init_engine(:DEALER, linger: linger)
|
|
8
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
9
|
+
_init_engine(:DEALER, linger: linger, backend: backend)
|
|
10
10
|
_attach(endpoints, default: :connect)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -17,8 +17,8 @@ module OMQ
|
|
|
17
17
|
include Readable
|
|
18
18
|
include Writable
|
|
19
19
|
|
|
20
|
-
def initialize(endpoints = nil, linger: 0)
|
|
21
|
-
_init_engine(:ROUTER, linger: linger)
|
|
20
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
21
|
+
_init_engine(:ROUTER, linger: linger, backend: backend)
|
|
22
22
|
_attach(endpoints, default: :bind)
|
|
23
23
|
end
|
|
24
24
|
|
data/lib/omq/routing/fan_out.rb
CHANGED
|
@@ -91,11 +91,20 @@ module OMQ
|
|
|
91
91
|
end
|
|
92
92
|
else
|
|
93
93
|
batch.each do |parts|
|
|
94
|
-
topic
|
|
94
|
+
topic = parts.first || EMPTY_BINARY
|
|
95
|
+
wire_bytes = nil
|
|
96
|
+
|
|
95
97
|
@connections.each do |conn|
|
|
96
98
|
next unless subscribed?(conn, topic)
|
|
97
99
|
begin
|
|
98
|
-
conn.
|
|
100
|
+
if conn.respond_to?(:curve?) && conn.curve?
|
|
101
|
+
conn.write_message(parts)
|
|
102
|
+
elsif conn.respond_to?(:write_wire)
|
|
103
|
+
wire_bytes ||= Protocol::ZMTP::Codec::Frame.encode_message(parts)
|
|
104
|
+
conn.write_wire(wire_bytes)
|
|
105
|
+
else
|
|
106
|
+
conn.write_message(parts)
|
|
107
|
+
end
|
|
99
108
|
@written << conn
|
|
100
109
|
rescue *CONNECTION_LOST
|
|
101
110
|
end
|
data/lib/omq/routing/radio.rb
CHANGED
|
@@ -98,12 +98,22 @@ module OMQ
|
|
|
98
98
|
end
|
|
99
99
|
else
|
|
100
100
|
batch.each do |parts|
|
|
101
|
-
group
|
|
102
|
-
body
|
|
101
|
+
group = parts[0]
|
|
102
|
+
body = parts[1] || EMPTY_BINARY
|
|
103
|
+
msg = [group, body]
|
|
104
|
+
wire_bytes = nil
|
|
105
|
+
|
|
103
106
|
@connections.each do |conn|
|
|
104
107
|
next unless @groups[conn]&.include?(group)
|
|
105
108
|
begin
|
|
106
|
-
conn.
|
|
109
|
+
if conn.respond_to?(:curve?) && conn.curve?
|
|
110
|
+
conn.write_message(msg)
|
|
111
|
+
elsif conn.respond_to?(:write_wire)
|
|
112
|
+
wire_bytes ||= Protocol::ZMTP::Codec::Frame.encode_message(msg)
|
|
113
|
+
conn.write_wire(wire_bytes)
|
|
114
|
+
else
|
|
115
|
+
conn.write_message(msg)
|
|
116
|
+
end
|
|
107
117
|
@written << conn
|
|
108
118
|
rescue *CONNECTION_LOST
|
|
109
119
|
end
|
data/lib/omq/scatter_gather.rb
CHANGED
|
@@ -5,8 +5,8 @@ module OMQ
|
|
|
5
5
|
include Writable
|
|
6
6
|
include SingleFrame
|
|
7
7
|
|
|
8
|
-
def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil)
|
|
9
|
-
_init_engine(:SCATTER, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout)
|
|
8
|
+
def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil, backend: nil)
|
|
9
|
+
_init_engine(:SCATTER, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout, backend: backend)
|
|
10
10
|
_attach(endpoints, default: :connect)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -15,8 +15,8 @@ module OMQ
|
|
|
15
15
|
include Readable
|
|
16
16
|
include SingleFrame
|
|
17
17
|
|
|
18
|
-
def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil)
|
|
19
|
-
_init_engine(:GATHER, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout)
|
|
18
|
+
def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil, backend: nil)
|
|
19
|
+
_init_engine(:GATHER, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout, backend: backend)
|
|
20
20
|
_attach(endpoints, default: :bind)
|
|
21
21
|
end
|
|
22
22
|
end
|
data/lib/omq/socket.rb
CHANGED
|
@@ -36,7 +36,8 @@ module OMQ
|
|
|
36
36
|
:heartbeat_ttl, :heartbeat_ttl=,
|
|
37
37
|
:heartbeat_timeout, :heartbeat_timeout=,
|
|
38
38
|
:max_message_size, :max_message_size=,
|
|
39
|
-
:mechanism, :mechanism
|
|
39
|
+
:mechanism, :mechanism=,
|
|
40
|
+
:tls_context, :tls_context=
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
# Creates a new socket and binds it to the given endpoint.
|
|
@@ -224,7 +225,8 @@ module OMQ
|
|
|
224
225
|
# @param linger [Integer]
|
|
225
226
|
#
|
|
226
227
|
def _init_engine(socket_type, linger:, send_hwm: nil, recv_hwm: nil,
|
|
227
|
-
send_timeout: nil, recv_timeout: nil, conflate: false
|
|
228
|
+
send_timeout: nil, recv_timeout: nil, conflate: false,
|
|
229
|
+
backend: nil)
|
|
228
230
|
@options = Options.new(linger: linger)
|
|
229
231
|
@options.send_hwm = send_hwm if send_hwm
|
|
230
232
|
@options.recv_hwm = recv_hwm if recv_hwm
|
|
@@ -233,7 +235,11 @@ module OMQ
|
|
|
233
235
|
@options.conflate = conflate
|
|
234
236
|
@recv_buffer = []
|
|
235
237
|
@recv_mutex = Mutex.new
|
|
236
|
-
@engine =
|
|
238
|
+
@engine = case backend
|
|
239
|
+
when nil, :ruby then Engine.new(socket_type, @options)
|
|
240
|
+
when :ffi then FFI::Engine.new(socket_type, @options)
|
|
241
|
+
else raise ArgumentError, "unknown backend: #{backend}"
|
|
242
|
+
end
|
|
237
243
|
end
|
|
238
244
|
end
|
|
239
245
|
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "io/stream"
|
|
7
|
+
|
|
8
|
+
module OMQ
|
|
9
|
+
module Transport
|
|
10
|
+
# TLS transport — TLS v1.3 on top of TCP.
|
|
11
|
+
#
|
|
12
|
+
# Requires the socket's +tls_context+ to be set to an
|
|
13
|
+
# +OpenSSL::SSL::SSLContext+ before bind or connect.
|
|
14
|
+
#
|
|
15
|
+
module TLS
|
|
16
|
+
TLS_PREFIX = "tls+tcp://"
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# Binds a TLS server.
|
|
20
|
+
#
|
|
21
|
+
# @param endpoint [String] e.g. "tls+tcp://127.0.0.1:5555" or "tls+tcp://*:0"
|
|
22
|
+
# @param engine [Engine]
|
|
23
|
+
# @return [Listener]
|
|
24
|
+
#
|
|
25
|
+
def bind(endpoint, engine)
|
|
26
|
+
ctx = require_context!(engine)
|
|
27
|
+
host, port = parse_endpoint(endpoint)
|
|
28
|
+
host = "0.0.0.0" if host == "*"
|
|
29
|
+
|
|
30
|
+
addrs = Addrinfo.getaddrinfo(host, port, nil, :STREAM, nil, ::Socket::AI_PASSIVE)
|
|
31
|
+
raise ::Socket::ResolutionError, "no addresses for #{host}" if addrs.empty?
|
|
32
|
+
|
|
33
|
+
servers = []
|
|
34
|
+
actual_port = nil
|
|
35
|
+
|
|
36
|
+
addrs.each do |addr|
|
|
37
|
+
server = TCPServer.new(addr.ip_address, actual_port || port)
|
|
38
|
+
actual_port ||= server.local_address.ip_port
|
|
39
|
+
servers << server
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
host_part = host.include?(":") ? "[#{host}]" : host
|
|
43
|
+
resolved = "#{TLS_PREFIX}#{host_part}:#{actual_port}"
|
|
44
|
+
Listener.new(resolved, servers, actual_port, ctx)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Connects to a TLS endpoint.
|
|
48
|
+
#
|
|
49
|
+
# @param endpoint [String] e.g. "tls+tcp://127.0.0.1:5555"
|
|
50
|
+
# @param engine [Engine]
|
|
51
|
+
# @return [void]
|
|
52
|
+
#
|
|
53
|
+
def connect(endpoint, engine)
|
|
54
|
+
ctx = require_context!(engine)
|
|
55
|
+
host, port = parse_endpoint(endpoint)
|
|
56
|
+
tcp_sock = TCPSocket.new(host, port)
|
|
57
|
+
|
|
58
|
+
ssl = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx)
|
|
59
|
+
ssl.sync_close = true
|
|
60
|
+
ssl.hostname = host
|
|
61
|
+
ssl.connect
|
|
62
|
+
|
|
63
|
+
engine.handle_connected(IO::Stream::Buffered.wrap(ssl), endpoint: endpoint)
|
|
64
|
+
rescue
|
|
65
|
+
tcp_sock&.close unless ssl&.sync_close
|
|
66
|
+
raise
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Validates and freezes the TLS context from engine options.
|
|
72
|
+
#
|
|
73
|
+
# The context SHOULD have min_version set to TLS1_3_VERSION.
|
|
74
|
+
# We cannot validate this because SSLContext#min_version is
|
|
75
|
+
# write-only in Ruby's OpenSSL binding.
|
|
76
|
+
#
|
|
77
|
+
# @return [OpenSSL::SSL::SSLContext]
|
|
78
|
+
# @raise [ArgumentError] if no context is set
|
|
79
|
+
#
|
|
80
|
+
def require_context!(engine)
|
|
81
|
+
ctx = engine.options.tls_context
|
|
82
|
+
raise ArgumentError, "tls_context must be set for tls+tcp:// endpoints" unless ctx
|
|
83
|
+
ctx.freeze unless ctx.frozen?
|
|
84
|
+
ctx
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Parses a tls+tcp:// endpoint URI into host and port.
|
|
88
|
+
#
|
|
89
|
+
# @param endpoint [String]
|
|
90
|
+
# @return [Array(String, Integer)]
|
|
91
|
+
#
|
|
92
|
+
def parse_endpoint(endpoint)
|
|
93
|
+
uri = URI.parse("http://#{endpoint.delete_prefix(TLS_PREFIX)}")
|
|
94
|
+
[uri.hostname, uri.port]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# A bound TLS listener.
|
|
99
|
+
#
|
|
100
|
+
class Listener
|
|
101
|
+
# @return [String] resolved endpoint with actual port
|
|
102
|
+
attr_reader :endpoint
|
|
103
|
+
|
|
104
|
+
# @return [Integer] bound port
|
|
105
|
+
attr_reader :port
|
|
106
|
+
|
|
107
|
+
# @return [Array<TCPServer>] bound server sockets
|
|
108
|
+
attr_reader :servers
|
|
109
|
+
|
|
110
|
+
# @return [OpenSSL::SSL::SSLContext]
|
|
111
|
+
attr_reader :ssl_context
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# @param endpoint [String] resolved endpoint URI
|
|
115
|
+
# @param servers [Array<TCPServer>]
|
|
116
|
+
# @param port [Integer] bound port number
|
|
117
|
+
# @param ssl_context [OpenSSL::SSL::SSLContext]
|
|
118
|
+
#
|
|
119
|
+
def initialize(endpoint, servers, port, ssl_context)
|
|
120
|
+
@endpoint = endpoint
|
|
121
|
+
@servers = servers
|
|
122
|
+
@port = port
|
|
123
|
+
@ssl_context = ssl_context
|
|
124
|
+
@tasks = []
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Registers accept loop tasks owned by the engine.
|
|
129
|
+
#
|
|
130
|
+
# @param tasks [Array<Async::Task>]
|
|
131
|
+
#
|
|
132
|
+
def accept_tasks=(tasks)
|
|
133
|
+
@tasks = tasks
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Stops the listener.
|
|
138
|
+
#
|
|
139
|
+
def stop
|
|
140
|
+
@tasks.each(&:stop)
|
|
141
|
+
@servers.each { |s| s.close rescue nil }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
data/lib/omq/version.rb
CHANGED
data/lib/omq/writable.rb
CHANGED
data/lib/omq.rb
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
require "protocol/zmtp"
|
|
10
10
|
require "io/stream"
|
|
11
|
+
require "openssl"
|
|
11
12
|
|
|
12
13
|
require_relative "omq/version"
|
|
13
14
|
|
|
@@ -26,6 +27,7 @@ module OMQ
|
|
|
26
27
|
Errno::ECONNABORTED,
|
|
27
28
|
Errno::ENOTCONN,
|
|
28
29
|
IO::Stream::ConnectionResetError,
|
|
30
|
+
OpenSSL::SSL::SSLError,
|
|
29
31
|
].freeze
|
|
30
32
|
|
|
31
33
|
# Errors raised when a peer cannot be reached.
|
|
@@ -42,6 +44,7 @@ end
|
|
|
42
44
|
# Transport
|
|
43
45
|
require_relative "omq/transport/inproc"
|
|
44
46
|
require_relative "omq/transport/tcp"
|
|
47
|
+
require_relative "omq/transport/tls"
|
|
45
48
|
require_relative "omq/transport/ipc"
|
|
46
49
|
|
|
47
50
|
# Core
|
|
@@ -71,6 +74,7 @@ require_relative "omq/routing/dish"
|
|
|
71
74
|
require_relative "omq/routing/peer"
|
|
72
75
|
require_relative "omq/single_frame"
|
|
73
76
|
require_relative "omq/engine"
|
|
77
|
+
require_relative "omq/queue_interface"
|
|
74
78
|
require_relative "omq/readable"
|
|
75
79
|
require_relative "omq/writable"
|
|
76
80
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -73,6 +73,7 @@ files:
|
|
|
73
73
|
- lib/omq/peer.rb
|
|
74
74
|
- lib/omq/pub_sub.rb
|
|
75
75
|
- lib/omq/push_pull.rb
|
|
76
|
+
- lib/omq/queue_interface.rb
|
|
76
77
|
- lib/omq/radio_dish.rb
|
|
77
78
|
- lib/omq/reactor.rb
|
|
78
79
|
- lib/omq/readable.rb
|
|
@@ -106,6 +107,7 @@ files:
|
|
|
106
107
|
- lib/omq/transport/inproc.rb
|
|
107
108
|
- lib/omq/transport/ipc.rb
|
|
108
109
|
- lib/omq/transport/tcp.rb
|
|
110
|
+
- lib/omq/transport/tls.rb
|
|
109
111
|
- lib/omq/version.rb
|
|
110
112
|
- lib/omq/writable.rb
|
|
111
113
|
homepage: https://github.com/zeromq/omq
|