toq 0.0.1
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 +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE.md +29 -0
- data/README.md +68 -0
- data/Rakefile +53 -0
- data/lib/toq/client/handler.rb +165 -0
- data/lib/toq/client.rb +249 -0
- data/lib/toq/exceptions.rb +152 -0
- data/lib/toq/message.rb +63 -0
- data/lib/toq/protocol.rb +101 -0
- data/lib/toq/proxy.rb +84 -0
- data/lib/toq/request.rb +59 -0
- data/lib/toq/response.rb +63 -0
- data/lib/toq/server/handler.rb +144 -0
- data/lib/toq/server.rb +276 -0
- data/lib/toq/version.rb +13 -0
- data/lib/toq.rb +13 -0
- data/spec/pems/cacert.pem +37 -0
- data/spec/pems/client/cert.pem +37 -0
- data/spec/pems/client/foo-cert.pem +39 -0
- data/spec/pems/client/foo-key.pem +51 -0
- data/spec/pems/client/key.pem +51 -0
- data/spec/pems/server/cert.pem +37 -0
- data/spec/pems/server/key.pem +51 -0
- data/spec/servers/basic.rb +3 -0
- data/spec/servers/server.rb +83 -0
- data/spec/servers/unix_socket.rb +8 -0
- data/spec/servers/with_ssl_primitives.rb +11 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/toq/client_spec.rb +397 -0
- data/spec/toq/exceptions_spec.rb +77 -0
- data/spec/toq/message_spec.rb +47 -0
- data/spec/toq/proxy_spec.rb +99 -0
- data/spec/toq/request_spec.rb +53 -0
- data/spec/toq/response_spec.rb +49 -0
- data/spec/toq/server_spec.rb +129 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3f490887ff8d26a2e18e5c010e286a6fc48ad85a9955471909496d78158ce4fc
|
4
|
+
data.tar.gz: f5c0d6ecf126970506d46a0416fd7effedbd590e99a9a5cb7b1601f0898af06d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 760fc6a92f3162b42e91c0c62f217431745887699f743182f91e8265a2557c02b16e8a5f7defbad2cb4ed06559fb9c204d3e49393feb3001793a740d975c02d3
|
7
|
+
data.tar.gz: b91e5bafb6631ecc1e09d777b2144f54735b7c6975422bb9c1b359a2a39d2294c6163142196288bc84ffec56c004209346e6e58cd5747e8167570fbb3110ef89
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# ChangeLog
|
data/LICENSE.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# License
|
2
|
+
|
3
|
+
Copyright (C) 2022, Ecsypno <https://ecsypno.com/>
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without modification,
|
7
|
+
are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer.
|
11
|
+
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
14
|
+
and/or other materials provided with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of the copyright holder nor the names of its contributors
|
17
|
+
may be used to endorse or promote products derived from this software
|
18
|
+
without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
21
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
22
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
24
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
25
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
26
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
27
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
29
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# Toq
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Version</th>
|
6
|
+
<td>0.0.1</td>
|
7
|
+
</tr>
|
8
|
+
<tr>
|
9
|
+
<th>Github page</th>
|
10
|
+
<td><a href="http://github.com/qadron/toq">http://github.com/qadron/toq</a></td>
|
11
|
+
<tr/>
|
12
|
+
<tr>
|
13
|
+
<th>Code Documentation</th>
|
14
|
+
<td><a href="http://rubydoc.info/github/qadron/toq/">http://rubydoc.info/github/qadron/toq/</a></td>
|
15
|
+
</tr>
|
16
|
+
<tr>
|
17
|
+
<th>Author</th>
|
18
|
+
<td><a href="mailto:tasos.laskos@gmail.com">Tasos Laskos</a></td>
|
19
|
+
</tr>
|
20
|
+
<tr>
|
21
|
+
<th>Copyright</th>
|
22
|
+
<td>2022 <a href="https://ecsypno.com">Ecsypno</a></td>
|
23
|
+
</tr>
|
24
|
+
<tr>
|
25
|
+
<th>License</th>
|
26
|
+
<td><a href="file.LICENSE.html">3-clause BSD</a></td>
|
27
|
+
</tr>
|
28
|
+
</table>
|
29
|
+
|
30
|
+
## Synopsis
|
31
|
+
|
32
|
+
Toq is a simple and lightweight Remote Procedure Call protocol and implementation.
|
33
|
+
|
34
|
+
This implementation is based on [Raktr](https://github.com/qadron/raktr).
|
35
|
+
|
36
|
+
## Features
|
37
|
+
|
38
|
+
- Extremely lightweight.
|
39
|
+
- Very simple design.
|
40
|
+
- TLS encryption.
|
41
|
+
- Configurable serializer.
|
42
|
+
- Can intercept RPC responses and translate them into native objects for
|
43
|
+
when using serializers that only support basic types, like JSON or MessagePack.
|
44
|
+
- Token-based authentication.
|
45
|
+
- Pure-Ruby.
|
46
|
+
- Multi-platform, tested on:
|
47
|
+
- Linux
|
48
|
+
- OSX
|
49
|
+
- Windows
|
50
|
+
|
51
|
+
## Installation
|
52
|
+
|
53
|
+
gem install toq
|
54
|
+
|
55
|
+
## Running the Specs
|
56
|
+
|
57
|
+
bundle install
|
58
|
+
rake spec
|
59
|
+
|
60
|
+
## Protocol specifications
|
61
|
+
|
62
|
+
You can find the RPC protocol specification at the
|
63
|
+
[Wiki](https://github.com/Arachni/arachni-rpc/wiki).
|
64
|
+
|
65
|
+
## License
|
66
|
+
|
67
|
+
Toq is provided under the 3-clause BSD license.
|
68
|
+
See the `LICENSE` file for more information.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Arachni-RPC project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Arachni-RPC
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require File.expand_path( File.dirname( __FILE__ ) ) + '/lib/toq/version'
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'rspec'
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
|
16
|
+
RSpec::Core::RakeTask.new
|
17
|
+
rescue
|
18
|
+
end
|
19
|
+
|
20
|
+
task default: [ :build, :spec ]
|
21
|
+
|
22
|
+
desc 'Generate docs'
|
23
|
+
task :docs do
|
24
|
+
outdir = "../toq"
|
25
|
+
sh "rm -rf #{outdir}"
|
26
|
+
sh "mkdir -p #{outdir}"
|
27
|
+
|
28
|
+
sh "yardoc -o #{outdir}"
|
29
|
+
|
30
|
+
sh "rm -rf .yardoc"
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'Clean up'
|
34
|
+
task :clean do
|
35
|
+
sh 'rm *.gem || true'
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'Build the toq gem.'
|
39
|
+
task build: [ :clean ] do
|
40
|
+
sh 'gem build toq.gemspec'
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Build and install the toq gem.'
|
44
|
+
task install: [ :build ] do
|
45
|
+
sh "gem install toq-#{Toq::VERSION}.gem"
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Push a new version to Rubygems'
|
49
|
+
task publish: [ :build ] do
|
50
|
+
sh "git tag -a v#{Toq::VERSION} -m 'Version #{Toq::VERSION}'"
|
51
|
+
sh "gem push toq-#{Toq::VERSION}.gem"
|
52
|
+
end
|
53
|
+
task release: [ :publish ]
|
@@ -0,0 +1,165 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Arachni-RPC project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Arachni-RPC EM
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Toq
|
10
|
+
class Client
|
11
|
+
|
12
|
+
# Transmits {Request} objects and calls callbacks once an {Response} is received.
|
13
|
+
#
|
14
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
15
|
+
class Handler < Raktr::Connection
|
16
|
+
include Toq::Protocol
|
17
|
+
|
18
|
+
# Default amount of tries for failed requests.
|
19
|
+
DEFAULT_TRIES = 9
|
20
|
+
|
21
|
+
# @return [Symbol] Status of the connection, can be:
|
22
|
+
#
|
23
|
+
# * `:idle` -- Just initialized.
|
24
|
+
# * `:ready` -- A connection has been established.
|
25
|
+
# * `:pending` -- Sending request and awaiting response.
|
26
|
+
# * `:done` -- Response received and callback invoked -- ready to be reused.
|
27
|
+
# * `:closed` -- Connection closed.
|
28
|
+
attr_reader :status
|
29
|
+
|
30
|
+
# @return [Exceptions::ConnectionError]
|
31
|
+
attr_reader :error
|
32
|
+
|
33
|
+
# Prepares an RPC connection and sets {#status} to `:idle`.
|
34
|
+
#
|
35
|
+
# @param [Hash] opts
|
36
|
+
# @option opts [Integer] :max_retries (9)
|
37
|
+
# Default amount of tries for failed requests.
|
38
|
+
#
|
39
|
+
# @option opts [Client] :base
|
40
|
+
# Client instance needed to {Client#push_connection push} ourselves
|
41
|
+
# back to its connection pool once we're done and we're ready to be reused.
|
42
|
+
def initialize( opts )
|
43
|
+
@opts = opts.dup
|
44
|
+
|
45
|
+
@max_retries = @opts[:max_retries] || DEFAULT_TRIES
|
46
|
+
@client = @opts[:client]
|
47
|
+
|
48
|
+
@opts[:tries] ||= 0
|
49
|
+
@tries ||= @opts[:tries]
|
50
|
+
|
51
|
+
@status = :idle
|
52
|
+
@request = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Sends an RPC request (i.e. performs an RPC call) and sets {#status}
|
56
|
+
# to `:pending`.
|
57
|
+
#
|
58
|
+
# @param [Request] req
|
59
|
+
def send_request( req )
|
60
|
+
@request = req
|
61
|
+
@status = :pending
|
62
|
+
super( req )
|
63
|
+
end
|
64
|
+
|
65
|
+
# @note Pushes itself to the client's connection pool to be re-used.
|
66
|
+
#
|
67
|
+
# Handles responses to RPC requests, calls its callback and sets {#status}
|
68
|
+
# to `:done`.
|
69
|
+
#
|
70
|
+
# @param [Toq::Response] res
|
71
|
+
def receive_response( res )
|
72
|
+
if res.exception?
|
73
|
+
res.obj = Exceptions.from_response( res )
|
74
|
+
end
|
75
|
+
|
76
|
+
@request.callback.call( res.obj ) if @request.callback
|
77
|
+
ensure
|
78
|
+
@request = nil # Help the GC out.
|
79
|
+
@error = nil # Help the GC out.
|
80
|
+
@status = :done
|
81
|
+
|
82
|
+
@opts[:tries] = @tries = 0
|
83
|
+
@client.push_connection self
|
84
|
+
end
|
85
|
+
|
86
|
+
# Handles closed connections, cleans up the SSL session, retries (if
|
87
|
+
# necessary) and sets {#status} to `:closed`.
|
88
|
+
#
|
89
|
+
# @private
|
90
|
+
def on_close( reason )
|
91
|
+
if @request
|
92
|
+
# If there is a request and a callback and the callback hasn't yet be
|
93
|
+
# called (i.e. not done) then we got here by error so retry.
|
94
|
+
if @request && @request.callback && !done?
|
95
|
+
if retry?
|
96
|
+
retry_request
|
97
|
+
else
|
98
|
+
@error = e = Exceptions::ConnectionError.new( "Connection closed [#{reason}]" )
|
99
|
+
@request.callback.call( e )
|
100
|
+
@client.connection_failed self
|
101
|
+
end
|
102
|
+
|
103
|
+
return
|
104
|
+
end
|
105
|
+
else
|
106
|
+
@error = reason
|
107
|
+
@client.connection_failed self
|
108
|
+
end
|
109
|
+
|
110
|
+
close_without_retry
|
111
|
+
end
|
112
|
+
|
113
|
+
# @note If `true`, the connection can be re-used.
|
114
|
+
#
|
115
|
+
# @return [Boolean]
|
116
|
+
# `true` when the connection is done, `false` otherwise.
|
117
|
+
def done?
|
118
|
+
@status == :done
|
119
|
+
end
|
120
|
+
|
121
|
+
# Closes the connection without triggering a retry operation and sets
|
122
|
+
# {#status} to `:closed`.
|
123
|
+
def close_without_retry
|
124
|
+
@request = nil
|
125
|
+
@status = :closed
|
126
|
+
close_without_callback
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# Converts incoming hash objects to {Response} objects and calls
|
132
|
+
# {#receive_response}.
|
133
|
+
#
|
134
|
+
# @param [Hash] obj
|
135
|
+
def receive_object( obj )
|
136
|
+
receive_response( Response.new( obj ) )
|
137
|
+
end
|
138
|
+
|
139
|
+
def retry_request
|
140
|
+
opts = @opts.dup
|
141
|
+
opts[:tries] += 1
|
142
|
+
|
143
|
+
req = @request.dup
|
144
|
+
|
145
|
+
# The connection will be detached soon, keep a separate reference to
|
146
|
+
# the reactor.
|
147
|
+
reactor = @reactor
|
148
|
+
|
149
|
+
@tries += 1
|
150
|
+
reactor.delay( 0.2 ) do
|
151
|
+
address = opts[:socket] ? opts[:socket] : [opts[:host], opts[:port]]
|
152
|
+
reactor.connect( *[address, self.class, opts ].flatten ).send_request( req )
|
153
|
+
end
|
154
|
+
|
155
|
+
close_without_retry
|
156
|
+
end
|
157
|
+
|
158
|
+
def retry?
|
159
|
+
@tries < @max_retries
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
data/lib/toq/client.rb
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Arachni-RPC project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Arachni-RPC EM
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Toq
|
10
|
+
|
11
|
+
require_relative 'client/handler'
|
12
|
+
|
13
|
+
# Simple RPC client capable of:
|
14
|
+
#
|
15
|
+
# * TLS encryption.
|
16
|
+
# * Asynchronous and synchronous requests.
|
17
|
+
# * Handling remote asynchronous calls that defer their result.
|
18
|
+
#
|
19
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
20
|
+
class Client
|
21
|
+
|
22
|
+
# Default amount of connections to maintain in the re-use pool.
|
23
|
+
DEFAULT_CONNECTION_POOL_SIZE = 1
|
24
|
+
|
25
|
+
# @return [Hash]
|
26
|
+
# Options hash.
|
27
|
+
attr_reader :opts
|
28
|
+
|
29
|
+
# @return [Integer]
|
30
|
+
# Amount of connections in the pool.
|
31
|
+
attr_reader :connection_count
|
32
|
+
|
33
|
+
# @example Example options:
|
34
|
+
#
|
35
|
+
# {
|
36
|
+
# :host => 'localhost',
|
37
|
+
# :port => 7331,
|
38
|
+
#
|
39
|
+
# # optional authentication token, if it doesn't match the one
|
40
|
+
# # set on the server-side you'll be getting exceptions.
|
41
|
+
# :token => 'superdupersecret',
|
42
|
+
#
|
43
|
+
# :serializer => Marshal,
|
44
|
+
#
|
45
|
+
# :max_retries => 0,
|
46
|
+
#
|
47
|
+
# # In order to enable peer verification one must first provide
|
48
|
+
# # the following:
|
49
|
+
# # SSL CA certificate
|
50
|
+
# :ssl_ca => cwd + '/../spec/pems/cacert.pem',
|
51
|
+
# # SSL private key
|
52
|
+
# :ssl_pkey => cwd + '/../spec/pems/client/key.pem',
|
53
|
+
# # SSL certificate
|
54
|
+
# :ssl_cert => cwd + '/../spec/pems/client/cert.pem'
|
55
|
+
# }
|
56
|
+
#
|
57
|
+
# @param [Hash] opts
|
58
|
+
# @option opts [String] :host Hostname/IP address.
|
59
|
+
# @option opts [Integer] :port Port number.
|
60
|
+
# @option opts [String] :socket Path to UNIX domain socket.
|
61
|
+
# @option opts [Integer] :connection_pool_size (1)
|
62
|
+
# Amount of connections to keep open.
|
63
|
+
# @option opts [String] :token Optional authentication token.
|
64
|
+
# @option opts [.dump, .load] :serializer (YAML)
|
65
|
+
# Serializer to use for message transmission.
|
66
|
+
# @option opts [Integer] :max_retries
|
67
|
+
# How many times to retry failed requests.
|
68
|
+
# @option opts [String] :ssl_ca SSL CA certificate.
|
69
|
+
# @option opts [String] :ssl_pkey SSL private key.
|
70
|
+
# @option opts [String] :ssl_cert SSL certificate.
|
71
|
+
def initialize( opts )
|
72
|
+
@opts = opts.merge( role: :client )
|
73
|
+
@token = @opts[:token]
|
74
|
+
|
75
|
+
@host, @port = @opts[:host], @opts[:port].to_i
|
76
|
+
@socket = @opts[:socket]
|
77
|
+
|
78
|
+
if !@socket && !(@host || @port)
|
79
|
+
fail ArgumentError, 'Needs either a :socket or :host and :port options.'
|
80
|
+
end
|
81
|
+
|
82
|
+
@port = @port.to_i
|
83
|
+
|
84
|
+
if @host && @port <= 0
|
85
|
+
fail ArgumentError, "Invalid port: #{@port}"
|
86
|
+
end
|
87
|
+
|
88
|
+
@pool_size = @opts[:connection_pool_size] || DEFAULT_CONNECTION_POOL_SIZE
|
89
|
+
|
90
|
+
@reactor = Raktr.global
|
91
|
+
|
92
|
+
@connections = @reactor.create_queue
|
93
|
+
@connection_count = 0
|
94
|
+
end
|
95
|
+
|
96
|
+
# Connection factory, will re-use or create new connections as needed to
|
97
|
+
# accommodate the workload.
|
98
|
+
#
|
99
|
+
# @param [Block] block
|
100
|
+
# Block to be passed a {Handler connection}.
|
101
|
+
#
|
102
|
+
# @return [Boolean]
|
103
|
+
# `true` if a new connection had to be established, `false` if an existing
|
104
|
+
# one was re-used.
|
105
|
+
def connect( &block )
|
106
|
+
ensure_reactor_running
|
107
|
+
|
108
|
+
if @connections.empty? && @connection_count < @pool_size
|
109
|
+
opts = @socket ? @socket : [@host, @port]
|
110
|
+
block.call @reactor.connect( *[opts, Handler, @opts.merge( client: self )].flatten )
|
111
|
+
increment_connection_counter
|
112
|
+
return true
|
113
|
+
end
|
114
|
+
|
115
|
+
pop_block = proc do |conn|
|
116
|
+
# Some connections may have died while they were waiting in the
|
117
|
+
# queue, get rid of them and start all over in case the queue has
|
118
|
+
# been emptied.
|
119
|
+
if !conn.done?
|
120
|
+
connection_failed conn
|
121
|
+
connect( &block )
|
122
|
+
next
|
123
|
+
end
|
124
|
+
|
125
|
+
block.call conn
|
126
|
+
end
|
127
|
+
|
128
|
+
@connections.pop( &pop_block )
|
129
|
+
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
133
|
+
# Close all connections.
|
134
|
+
def close
|
135
|
+
ensure_reactor_running
|
136
|
+
|
137
|
+
@reactor.on_tick do |task|
|
138
|
+
@connections.pop(&:close_without_retry)
|
139
|
+
task.done if @connections.empty?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def increment_connection_counter
|
144
|
+
@connection_count += 1
|
145
|
+
end
|
146
|
+
|
147
|
+
# {Handler#done? Finished} {Handler}s push themselves here to be re-used.
|
148
|
+
#
|
149
|
+
# @param [Handler] connection
|
150
|
+
def push_connection( connection )
|
151
|
+
ensure_reactor_running
|
152
|
+
|
153
|
+
@connections << connection
|
154
|
+
end
|
155
|
+
|
156
|
+
# Handles failed connections.
|
157
|
+
#
|
158
|
+
# @param [Handler] connection
|
159
|
+
def connection_failed( connection )
|
160
|
+
ensure_reactor_running
|
161
|
+
|
162
|
+
@connection_count -= 1
|
163
|
+
connection.close_without_retry
|
164
|
+
end
|
165
|
+
|
166
|
+
# Calls a remote method and grabs the result.
|
167
|
+
#
|
168
|
+
# There are 2 ways to perform a call, async (non-blocking) and sync (blocking).
|
169
|
+
#
|
170
|
+
# @example To perform an async call you need to provide a block to handle the result.
|
171
|
+
#
|
172
|
+
# server.call( 'handler.method', arg1, arg2 ) do |res|
|
173
|
+
# do_stuff( res )
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
# @example To perform a sync (blocking), call without a block.
|
177
|
+
#
|
178
|
+
# res = server.call( 'handler.method', arg1, arg2 )
|
179
|
+
#
|
180
|
+
# @param [String] msg
|
181
|
+
# RPC message in the form of `handler.method`.
|
182
|
+
# @param [Array] args
|
183
|
+
# Collection of arguments to be passed to the method.
|
184
|
+
# @param [Block] block
|
185
|
+
def call( msg, *args, &block )
|
186
|
+
ensure_reactor_running
|
187
|
+
|
188
|
+
req = Request.new(
|
189
|
+
message: msg,
|
190
|
+
args: args,
|
191
|
+
callback: block,
|
192
|
+
token: @token
|
193
|
+
)
|
194
|
+
|
195
|
+
block_given? ? call_async( req ) : call_sync( req )
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def set_exception( req, e )
|
201
|
+
msg = @socket ? " for '#{@socket}'." : " for '#{@host}:#{@port}'."
|
202
|
+
|
203
|
+
exc = (e.is_a?( Raktr::Connection::Error::SSL ) ?
|
204
|
+
Exceptions::SSLPeerVerificationFailed : Exceptions::ConnectionError
|
205
|
+
).new( e.to_s + msg )
|
206
|
+
|
207
|
+
exc.set_backtrace e.backtrace
|
208
|
+
req.callback.call exc
|
209
|
+
end
|
210
|
+
|
211
|
+
def call_async( req, &block )
|
212
|
+
req.callback = block if block_given?
|
213
|
+
|
214
|
+
begin
|
215
|
+
connect do |connection|
|
216
|
+
error = (connection.is_a?( Exception ) and connection) || connection.error
|
217
|
+
next set_exception( req, error ) if error
|
218
|
+
|
219
|
+
connection.send_request( req )
|
220
|
+
end
|
221
|
+
rescue => e
|
222
|
+
set_exception( req, e )
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def call_sync( req )
|
227
|
+
# If we're in the Reactor thread use a Fiber and if we're not use a Thread.
|
228
|
+
if @reactor.in_same_thread?
|
229
|
+
fail 'Cannot perform synchronous calls when running in the ' +
|
230
|
+
"#{Raktr} loop."
|
231
|
+
end
|
232
|
+
|
233
|
+
q = Queue.new
|
234
|
+
call_async( req ) { |obj| q << obj }
|
235
|
+
ret = q.pop
|
236
|
+
|
237
|
+
raise ret if ret.is_a?( Exception )
|
238
|
+
|
239
|
+
ret
|
240
|
+
end
|
241
|
+
|
242
|
+
def ensure_reactor_running
|
243
|
+
return if @reactor.running?
|
244
|
+
@reactor.run_in_thread
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|