farcall 0.1.2 → 0.3.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/README.md +38 -7
- data/farcall.gemspec +8 -5
- data/lib/farcall/boss_transport.rb +3 -1
- data/lib/farcall/em_farcall.rb +248 -0
- data/lib/farcall/em_wsserver_endpoint.rb +73 -0
- data/lib/farcall/endpoint.rb +61 -17
- data/lib/farcall/monitor_lock.rb +74 -0
- data/lib/farcall/transport.rb +3 -1
- data/lib/farcall/version.rb +1 -1
- data/lib/farcall/wsclient_transport.rb +55 -0
- data/lib/farcall.rb +3 -0
- data/spec/endpoint_spec.rb +41 -4
- data/spec/websock_spec.rb +233 -0
- metadata +54 -8
- data/spec/rmi_spec.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ccf7c600e32b64b16b3ee5e278064bef7214274
|
4
|
+
data.tar.gz: 2e7fc842321b53483a09b02caf9cc9c8fc8af85f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 611fea3359ae86917d1625f8b1daf79e78c4cf20421a8ee64d3e679a00fbe0007a8bd79a551fb9e24bf6d38e3eea47dc9e77ac92927cb05c7c66f51d0ae8749e
|
7
|
+
data.tar.gz: 4010ab7cf9dbe52c2e4ca519c3df823a5ea104ff7783118340e576ebd2551c0866b20967c55b82e0a8a23e00a7314a73362efb433db6da9f092a8386c1388afe
|
data/README.md
CHANGED
@@ -1,11 +1,23 @@
|
|
1
1
|
# Farcall
|
2
2
|
|
3
|
+
## News
|
4
|
+
|
5
|
+
Since 0.3.0 farcall gem provides websocket client and server out of the box.
|
6
|
+
|
3
7
|
## Description
|
4
8
|
|
5
9
|
The simple and elegant cross-platform RPC protocol that uses any formatter/transport capable of
|
6
10
|
transmitting dictionary-like objects, for example, JSON,
|
7
11
|
[BOSS](https://github.com/sergeych/boss_protocol), XML, BSON and many others. This gem
|
8
|
-
supports out of the box JSON and [BOSS](https://github.com/sergeych/boss_protocol) protocols
|
12
|
+
supports out of the box JSON and [BOSS](https://github.com/sergeych/boss_protocol) protocols and
|
13
|
+
streams and sockets as the media.
|
14
|
+
|
15
|
+
There is also optional support for eventmachine based wbesocket server and regular client websocket
|
16
|
+
connection. All you need is to include gem 'em-websocket' and/or gem 'websocket-client-simple'.
|
17
|
+
All websocket implementations use JSON encoding to vbe interoperable with most web allications.
|
18
|
+
|
19
|
+
We do not include them in the dependencies because eventmachine is big and does not work with jruby,
|
20
|
+
and websocket client is not always needed and we are fond of minimizing dependencies.
|
9
21
|
|
10
22
|
RPC is made asynchronously, each call can have any return values. While one call is waiting,
|
11
23
|
other calls can be executed. The protocol is bidirectional Call parameters could be
|
@@ -15,7 +27,7 @@ dictionary, wahtever.
|
|
15
27
|
Exception/errors transmitting is also supported. The interface is very simple and rubyish. The
|
16
28
|
protocol is very easy to implement if there is no implementation, see
|
17
29
|
[Farcall protocol specification](https://github.com/sergeych/farcall/wiki). Java library for
|
18
|
-
Android and desktop is
|
30
|
+
Android and desktop is ready upon request (leave me a task or a message in th github).
|
19
31
|
|
20
32
|
## Installation
|
21
33
|
|
@@ -23,8 +35,15 @@ Add this line to your application's Gemfile:
|
|
23
35
|
|
24
36
|
```ruby
|
25
37
|
gem 'farcall'
|
26
|
-
# If you want to use binary-effective boss encoding:
|
27
|
-
# gem '
|
38
|
+
# If you want to use binary-effective boss encoding, uncomment:
|
39
|
+
# gem 'boss-protocol', '>= 1.4.1'
|
40
|
+
#
|
41
|
+
# if you want to use eventmachine and server websocket, uncomment:
|
42
|
+
# gem 'em-websocket'
|
43
|
+
#
|
44
|
+
# To use websocket client, uncomment
|
45
|
+
# gem 'websocket-client-simple'
|
46
|
+
|
28
47
|
```
|
29
48
|
|
30
49
|
And then execute:
|
@@ -59,8 +78,16 @@ Or install it yourself as:
|
|
59
78
|
TestProvider.new socket: connected_socket, format: :boss
|
60
79
|
```
|
61
80
|
|
62
|
-
|
63
|
-
|
81
|
+
`Farcall::Provider` provides easy constructors to use it with the transport or the endpoint.
|
82
|
+
If you need to implement farcall over somw other media, just extend `TestTransport` and provide
|
83
|
+
`send_data` and call `on_received_data` when need. It's very simple and straightforward.
|
84
|
+
|
85
|
+
Consult [online documentation for Transport](http://www.rubydoc.info/gems/farcall/Farcall/Transport)
|
86
|
+
and [Provider](http://www.rubydoc.info/gems/farcall/Farcall/Provider) for more.
|
87
|
+
|
88
|
+
In the most common case you just have to connect two sockets, in which case everythng works right
|
89
|
+
out of the box. Suppose whe have some socket connected to one above, then TestProvider methods are
|
90
|
+
available via this connection:
|
64
91
|
|
65
92
|
```ruby
|
66
93
|
|
@@ -120,11 +147,15 @@ So, I would recommend:
|
|
120
147
|
|
121
148
|
- if you need BOSS but can't find it on your platform, use both and contact me :)
|
122
149
|
|
150
|
+
## Usage with eventmacine
|
151
|
+
|
152
|
+
You can use `EmFarcall::Endpoint` widely as it uses a pair of EM::Channel as a trasnport, e.g.
|
153
|
+
it could be easily connected to any evented data source.
|
154
|
+
|
123
155
|
## Documentation
|
124
156
|
|
125
157
|
* [Farcall protocol](https://github.com/sergeych/farcall/wiki)
|
126
158
|
|
127
|
-
|
128
159
|
* Gem [online docs](http://www.rubydoc.info/gems/farcall)
|
129
160
|
|
130
161
|
## Contributing
|
data/farcall.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
pass structures (dictionaries, hashes, whatever you name it). Out of the box provides
|
15
15
|
JSON and BOSS formats over streams and sockets.
|
16
16
|
End
|
17
|
-
spec.homepage = ""
|
17
|
+
spec.homepage = "https://github.com/sergeych/farcall"
|
18
18
|
spec.license = "MIT"
|
19
19
|
|
20
20
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -22,8 +22,11 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
-
spec.
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
25
|
+
spec.add_dependency 'hashie'
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
spec.add_development_dependency 'rspec'
|
29
|
+
spec.add_development_dependency 'em-websocket'
|
30
|
+
spec.add_development_dependency 'websocket-client-simple'
|
31
|
+
spec.add_development_dependency 'boss-protocol', '>= 1.4.3'
|
29
32
|
end
|
@@ -9,6 +9,8 @@ module Farcall
|
|
9
9
|
# Create json transport, see Farcall::Transpor#create for parameters
|
10
10
|
def initialize **params
|
11
11
|
setup_streams **params
|
12
|
+
@formatter = Boss::Formatter.new(@output)
|
13
|
+
@formatter.set_stream_mode
|
12
14
|
end
|
13
15
|
|
14
16
|
def on_data_received= block
|
@@ -21,7 +23,7 @@ module Farcall
|
|
21
23
|
end
|
22
24
|
|
23
25
|
def send_data hash
|
24
|
-
@
|
26
|
+
@formatter << hash
|
25
27
|
end
|
26
28
|
|
27
29
|
def close
|
@@ -0,0 +1,248 @@
|
|
1
|
+
begin
|
2
|
+
require 'hashie'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
# As the eventmachine callback paradigm is completely different from the threaded paradigm
|
6
|
+
# of the Farcall, that runs pretty well under JRuby and in multithreaded MRI, we provide
|
7
|
+
# compatible but different implementations: {EmFarcall::Endpoint}, {EmFarcall::Interface}
|
8
|
+
# and {EmFarcall::Provider}. Changes to adapt these are minimal except of the callback
|
9
|
+
# paradigm. The rest is the same.
|
10
|
+
#
|
11
|
+
module EmFarcall
|
12
|
+
|
13
|
+
# Endpoint that run in the reactor thread of the EM. Eventmachine should rin by the time of
|
14
|
+
# creation of the endpoint. All the methods can be called from any thread, not only
|
15
|
+
# EM's reactor thread.
|
16
|
+
#
|
17
|
+
# As the eventmachine callback paradigm is completely different from the threaded paradigm
|
18
|
+
# of the Farcall, that runs pretty well under
|
19
|
+
# JRuby and in multithreaded MRI, we provide
|
20
|
+
# compatible but different endpoint to run under EM.
|
21
|
+
#
|
22
|
+
# Its main difference is that there is no sync_call, instead, calling remote commands
|
23
|
+
# from the endpoint and/ot interface can provide blocks that are called when the remote
|
24
|
+
# is executed.
|
25
|
+
#
|
26
|
+
# The EM version of the endpoint works with any 2 EM:C:Channels.
|
27
|
+
#
|
28
|
+
class Endpoint
|
29
|
+
|
30
|
+
# Create new endpoint to work with input and output channels
|
31
|
+
#
|
32
|
+
# @param [EM::Channel] input_channel
|
33
|
+
# @param [EM::Channel] output_channel
|
34
|
+
def initialize(input_channel, output_channel, errback=nil, provider: nil)
|
35
|
+
EM.schedule {
|
36
|
+
@input, @output, @errback = input_channel, output_channel, errback
|
37
|
+
@trace = false
|
38
|
+
@in_serial = @out_serial = 0
|
39
|
+
@callbacks = {}
|
40
|
+
@handlers = {}
|
41
|
+
@unnamed_handler = -> (name, *args, **kwargs) {
|
42
|
+
raise NoMethodError, "method does not exist: #{name}"
|
43
|
+
}
|
44
|
+
@input.subscribe { |data|
|
45
|
+
process_input(data)
|
46
|
+
}
|
47
|
+
if provider
|
48
|
+
@provider = provider
|
49
|
+
provider.endpoint = self
|
50
|
+
end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set or get provider instance. When provider is set, its public methods are called by the remote
|
55
|
+
# and any possible exception are passed back to caller party. You can use any ruby class instance
|
56
|
+
# everything will work, operators, indexes[] and like.
|
57
|
+
attr_accessor :provider
|
58
|
+
|
59
|
+
# Call remote with specified name and arguments calling block when done.
|
60
|
+
# if block is provided, it will be called when the remote will be called and possibly return
|
61
|
+
# some data.
|
62
|
+
#
|
63
|
+
# Block receives single object paramter with two fields: `result.error` and `result.result`.
|
64
|
+
#
|
65
|
+
# `result.error` is not nil when the remote raised error, then `error[:class]` and
|
66
|
+
# `error.text` are set accordingly.
|
67
|
+
#
|
68
|
+
# if error is nil then result.result receives any return data from the remote method.
|
69
|
+
#
|
70
|
+
# for example:
|
71
|
+
#
|
72
|
+
# endpoint.call( 'some_func', 10, 20) { |done|
|
73
|
+
# if done.error
|
74
|
+
# puts "Remote error class: #{done.error[:class]}: #{done.error.text}"
|
75
|
+
# else
|
76
|
+
# puts "Remote returned #{done.result}"
|
77
|
+
# }
|
78
|
+
#
|
79
|
+
# @param [String] name command name
|
80
|
+
# @return [Endpoint] self
|
81
|
+
def call(name, *args, **kwargs, &block)
|
82
|
+
EM.schedule {
|
83
|
+
if block
|
84
|
+
@callbacks[@in_serial] = block
|
85
|
+
end
|
86
|
+
send_block cmd: name, args: args, kwargs: kwargs
|
87
|
+
}
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# Close the endpoint
|
92
|
+
def close
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
# Report error via errback and the endpoint
|
97
|
+
def error text
|
98
|
+
STDERR.puts "farcall ws server error #{text}"
|
99
|
+
EM.schedule {
|
100
|
+
@errback.call(text) if @errback
|
101
|
+
close
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set handler to perform the named command. Block will be called when the remote party calls
|
106
|
+
# with parameters passed from the remote. The block returned value will be passed back to
|
107
|
+
# the caller.
|
108
|
+
#
|
109
|
+
# If the block raises the exception it will be reported to the caller as an error (depending
|
110
|
+
# on it's platofrm, will raise exception on its end or report error)
|
111
|
+
def on(name, &block)
|
112
|
+
@handlers[name.to_s] = block
|
113
|
+
end
|
114
|
+
|
115
|
+
# Process remote command. First parameter passed to the block is the method name, the rest
|
116
|
+
# are optional arguments of the call:
|
117
|
+
#
|
118
|
+
# endpoint.on_command { |name, *args, **kwargs|
|
119
|
+
# if name == 'echo'
|
120
|
+
# { args: args, keyword_args: kwargs }
|
121
|
+
# else
|
122
|
+
# raise "unknown command"
|
123
|
+
# end
|
124
|
+
# }
|
125
|
+
#
|
126
|
+
# raising exceptions from the block cause farcall error to be returned back th the caller.
|
127
|
+
def on_command &block
|
128
|
+
raise "unnamed handler should be present" unless block
|
129
|
+
@unnamed_handler = block
|
130
|
+
end
|
131
|
+
|
132
|
+
# Same as #on_command (compatibilty method)
|
133
|
+
def on_remote_call &block
|
134
|
+
on_command block
|
135
|
+
end
|
136
|
+
|
137
|
+
# Get the Farcall::RemoteInterface connnected to this endpoint. Any subsequent calls with
|
138
|
+
# return the same instance.
|
139
|
+
def remote
|
140
|
+
@remote ||= EmFarcall::Interface.new endpoint: self
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# :nodoc: sends block with correct framing
|
146
|
+
def send_block **data
|
147
|
+
data[:serial] = @out_serial
|
148
|
+
@out_serial += 1
|
149
|
+
@output << data
|
150
|
+
end
|
151
|
+
|
152
|
+
# :nodoc:
|
153
|
+
def execute_command cmd, ref, args, kwargs
|
154
|
+
kwargs = (kwargs || {}).inject({}) {
|
155
|
+
|all, kv|
|
156
|
+
all[kv[0].to_sym] = kv[1]
|
157
|
+
all
|
158
|
+
}
|
159
|
+
args << kwargs if kwargs && !kwargs.empty?
|
160
|
+
result = if proc = @handlers[cmd.to_s]
|
161
|
+
proc.call(*args)
|
162
|
+
elsif @provider
|
163
|
+
provider.send :remote_call, cmd.to_sym, args
|
164
|
+
else
|
165
|
+
@unnamed_handler.call(cmd, args)
|
166
|
+
end
|
167
|
+
send_block ref: ref, result: result
|
168
|
+
|
169
|
+
rescue
|
170
|
+
if @trace
|
171
|
+
puts $!
|
172
|
+
puts $!.backtrace.join("\n")
|
173
|
+
end
|
174
|
+
send_block ref: ref, error: { class: $!.class.name, text: $!.to_s }
|
175
|
+
end
|
176
|
+
|
177
|
+
# :nodoc: important that this method is called from reactor thread only
|
178
|
+
def process_input data
|
179
|
+
# To be free from :keys and 'keys'
|
180
|
+
data = Hashie::Mash.new(data) unless data.is_a?(Hashie::Mash)
|
181
|
+
if data.serial != @in_serial
|
182
|
+
error "framing error (wrong serial:)"
|
183
|
+
else
|
184
|
+
@in_serial += 1
|
185
|
+
if (cmd = data.cmd) != nil
|
186
|
+
execute_command(cmd, data.serial, data.args || [], data.kwargs || {})
|
187
|
+
else
|
188
|
+
ref = data.ref
|
189
|
+
if ref
|
190
|
+
if (block = @callbacks.delete(ref)) != nil
|
191
|
+
block.call(Hashie::Mash.new(result: data.result, error: data.error))
|
192
|
+
end
|
193
|
+
else
|
194
|
+
error "framing error: no ref in block #{data.inspect}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Interface to the remote provider via Farcall protocols. Works the same as if the object
|
202
|
+
# is local and yields block in return, unlike Farcall::Interface that blocks
|
203
|
+
#
|
204
|
+
# RemoteInterface transparently creates methods as you call them to speedup subsequent
|
205
|
+
# calls.
|
206
|
+
#
|
207
|
+
class Interface
|
208
|
+
|
209
|
+
# Create interface connected to some endpoint ar transpost.
|
210
|
+
#
|
211
|
+
# Please remember that Farcall::Transport instance could be used with only
|
212
|
+
# one connected object, unlike Farcall::Endpoint, which could be connected to several
|
213
|
+
# consumers.
|
214
|
+
#
|
215
|
+
# @param [Farcall::Endpoint|Farcall::Transport] arg either endpoint or a transport
|
216
|
+
# to connect interface to
|
217
|
+
def initialize(endpoint)
|
218
|
+
@endpoint = endpoint
|
219
|
+
end
|
220
|
+
|
221
|
+
def method_missing(method_name, *arguments, **kw_arguments, &block)
|
222
|
+
instance_eval <<-End
|
223
|
+
def #{method_name} *arguments, **kw_arguments, &block
|
224
|
+
@endpoint.call '#{method_name}', *arguments, **kw_arguments, &block
|
225
|
+
end
|
226
|
+
End
|
227
|
+
@endpoint.call method_name, *arguments, **kw_arguments, &block
|
228
|
+
end
|
229
|
+
|
230
|
+
def respond_to_missing?(method_name, include_private = false)
|
231
|
+
true
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class Provider < Farcall::Provider
|
236
|
+
|
237
|
+
attr_accessor :endpoint
|
238
|
+
|
239
|
+
def far_interface
|
240
|
+
endpoint.remote
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
end
|
245
|
+
rescue LoadError
|
246
|
+
$!.to_s =~ /eventmachine/ or raise
|
247
|
+
end
|
248
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
begin
|
2
|
+
require 'em-websocket'
|
3
|
+
require 'eventmachine'
|
4
|
+
require_relative './em_farcall'
|
5
|
+
|
6
|
+
module EmFarcall
|
7
|
+
|
8
|
+
# Farcall websocket client. To use it you must add to your Gemfile:
|
9
|
+
#
|
10
|
+
# gem 'websocket-client-simple'
|
11
|
+
#
|
12
|
+
# then you can use it with EM:
|
13
|
+
#
|
14
|
+
# endpoint = nil
|
15
|
+
#
|
16
|
+
# EM::WebSocket.run(params) do |ws|
|
17
|
+
# ws.onopen { |handshake|
|
18
|
+
# # Check handshake.path, handshake query
|
19
|
+
# # for example to select the provider, then connect Farcall to websocket:
|
20
|
+
# endpoint = EmFarcall::WsServerEndpoint.new ws, provider: WsProvider.new
|
21
|
+
# }
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# now we can use it as usual: remote can call provder method, and we can call remote:
|
25
|
+
#
|
26
|
+
# endpoint.remote.do_something( times: 4) { |result|
|
27
|
+
# }
|
28
|
+
#
|
29
|
+
#
|
30
|
+
# We do not include it into gem dependencies as it uses EventMachine
|
31
|
+
# which is not needed under JRuby and weight alot (the resto of Farcall plays well with jruby
|
32
|
+
# and MRI threads)
|
33
|
+
#
|
34
|
+
# Due to event-driven nature of eventmachine, WsServerEndpoint uses special version of
|
35
|
+
# {EmFarcall::Endpoint} and {EmFarcall::WsProvider} which are code compatible with regular
|
36
|
+
# farcall classes except for the callback-style calls where appropriate.
|
37
|
+
class WsServerEndpoint < EmFarcall::Endpoint
|
38
|
+
|
39
|
+
# Create endpoint with the already opened websocket instance. Note that all the handshake
|
40
|
+
# should be done prior to construct endpoint (e.g. you may want to have different endpoints
|
41
|
+
# for different paths and arguments)
|
42
|
+
#
|
43
|
+
# See {EmFarcall::Endpoint} for methods to call remote interface and process remote requests.
|
44
|
+
#
|
45
|
+
# @param [EM::WebSocket] websocket socket in open state (handshake should be passed)
|
46
|
+
def initialize websocket, **kwargs
|
47
|
+
@input = EM::Channel.new
|
48
|
+
@output = EM::Channel.new
|
49
|
+
super(@input, @output, **kwargs)
|
50
|
+
|
51
|
+
websocket.onmessage { |data|
|
52
|
+
@input << unpack(data)
|
53
|
+
}
|
54
|
+
@output.subscribe { |data|
|
55
|
+
websocket.send(pack data)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def unpack data
|
60
|
+
JSON.parse data
|
61
|
+
end
|
62
|
+
|
63
|
+
def pack data
|
64
|
+
JSON[data]
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
rescue LoadError
|
71
|
+
$!.to_s =~ /em-websocket/ or raise
|
72
|
+
end
|
73
|
+
|
data/lib/farcall/endpoint.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
1
3
|
module Farcall
|
2
4
|
|
3
5
|
# The protocol endpoint. Takes some transport and implements Farcall protocol over
|
4
6
|
# it. You can use it direcly or with Farcall::RemoteInterface and Farcall::LocalProvider helper
|
5
7
|
# classes.
|
6
8
|
#
|
9
|
+
# Note that the returned data is converted to Hashie::Mash primarily for the sake of :key vs.
|
10
|
+
# 'key' ambigity that otherwise might appear depending on the transport encoding protocol. Anyway
|
11
|
+
# it is better than ruby hash ;)
|
12
|
+
#
|
7
13
|
# Endpoint class is thread-safe.
|
8
14
|
class Endpoint
|
9
15
|
|
10
|
-
# Set or get provider instance. When provider is set, its methods are called by the remote
|
16
|
+
# Set or get provider instance. When provider is set, its public methods are called by the remote
|
11
17
|
# and any possible exception are passed back to caller party. You can use any ruby class instance
|
12
18
|
# everything will work, operators, indexes[] and like.
|
13
19
|
attr_accessor :provider
|
@@ -59,8 +65,9 @@ module Farcall
|
|
59
65
|
end
|
60
66
|
|
61
67
|
# Call remote party. Retruns immediately. When remote party answers, calls the specified block
|
62
|
-
# if present. The block should take |error, result| parameters.
|
63
|
-
#
|
68
|
+
# if present. The block should take |error, result| parameters. If result's content hashes
|
69
|
+
# or result itself are instances of th Hashie::Mash. Error could be nil or
|
70
|
+
# {'class' =>, 'text' => } Hashie::Mash hash. result is always nil if error is presented.
|
64
71
|
#
|
65
72
|
# It is desirable to use Farcall::Endpoint#interface or
|
66
73
|
# Farcall::RemoteInterface rather than this low-level method.
|
@@ -84,7 +91,8 @@ module Farcall
|
|
84
91
|
# Farcall::RemoteInterface rather than this low-level method.
|
85
92
|
#
|
86
93
|
# @param [String] name of the remote command
|
87
|
-
# @return [Object] any data that remote party retruns
|
94
|
+
# @return [Object] any data that remote party retruns. If it is a hash, it is a Hashie::Mash
|
95
|
+
# instance.
|
88
96
|
# @raise [Farcall::RemoteError]
|
89
97
|
#
|
90
98
|
def sync_call(name, *args, **kwargs)
|
@@ -143,12 +151,14 @@ module Farcall
|
|
143
151
|
|
144
152
|
def _received(data)
|
145
153
|
# p [:r, data]
|
154
|
+
data = Hashie::Mash.new data
|
155
|
+
|
146
156
|
cmd, serial, args, kwargs, ref, result, error =
|
147
157
|
%w{cmd serial args kwargs ref result error}.map { |k| data[k] || data[k.to_sym] }
|
148
158
|
!serial || serial < 0 and abort 'missing or bad serial'
|
149
159
|
|
150
160
|
@receive_lock.synchronize {
|
151
|
-
serial == @in_serial or abort "
|
161
|
+
serial == @in_serial or abort "framing error (wrong serial)"
|
152
162
|
@in_serial += 1
|
153
163
|
}
|
154
164
|
|
@@ -161,15 +171,18 @@ module Farcall
|
|
161
171
|
if kwargs && !kwargs.empty?
|
162
172
|
# ruby thing: keyqord args must be symbols, not strings:
|
163
173
|
fixed = {}
|
164
|
-
kwargs.each { |k,v| fixed[k.to_sym] = v}
|
174
|
+
kwargs.each { |k, v| fixed[k.to_sym] = v }
|
165
175
|
args << fixed
|
166
176
|
end
|
167
|
-
@provider.send cmd.to_sym,
|
177
|
+
@provider.send :remote_call, cmd.to_sym, args
|
168
178
|
elsif @on_remote_call
|
169
179
|
@on_remote_call.call cmd, args, kwargs
|
170
180
|
end
|
171
181
|
_send ref: serial, result: result
|
172
182
|
rescue Exception => e
|
183
|
+
# puts e
|
184
|
+
# puts e.backtrace.join("\n")
|
185
|
+
|
173
186
|
_send ref: serial, error: { 'class' => e.class.name, 'text' => e.to_s }
|
174
187
|
end
|
175
188
|
|
@@ -195,23 +208,54 @@ module Farcall
|
|
195
208
|
# suites you better.
|
196
209
|
#
|
197
210
|
# Please remember that Farcall::Transport instance could be used with only
|
198
|
-
# one
|
211
|
+
# one connected object, unlike Farcall::Endpoint, which could be connected to several
|
199
212
|
# consumers.
|
213
|
+
#
|
214
|
+
# @param [Farcall::Endpoint] endpoint to connect to (no transport should be provided).
|
215
|
+
# note that if endpoint is specified, transport would be ignored eeven if used
|
216
|
+
# @param [Farcall::Transport] transport to use (don't use endpoint then)
|
200
217
|
def initialize endpoint: nil, transport: nil, **params
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
218
|
+
if endpoint || transport || params.size > 0
|
219
|
+
@endpoint = if endpoint
|
220
|
+
endpoint
|
221
|
+
else
|
222
|
+
transport ||= Farcall::Transport.create **params
|
223
|
+
Farcall::Endpoint.new transport
|
224
|
+
end
|
225
|
+
@endpoint.provider = self
|
226
|
+
end
|
208
227
|
end
|
209
228
|
|
210
229
|
# Get remote interface
|
211
|
-
# @return [Farcall::Interface] to call methods on the other end
|
230
|
+
# @return [Farcall::Interface] to call methods on the other end, e.g. if this provider would
|
231
|
+
# like to call other party's methiod, it can do it cimply by:
|
232
|
+
#
|
233
|
+
# far_interface.some_method('hello')
|
234
|
+
#
|
212
235
|
def far_interface
|
213
236
|
@endpoint.remote
|
214
237
|
end
|
238
|
+
|
239
|
+
# close connection if need
|
240
|
+
def close_connection
|
241
|
+
@endpoint.close
|
242
|
+
end
|
243
|
+
|
244
|
+
protected
|
245
|
+
|
246
|
+
# Override it to repond to remote calls. Base implementation let invoke only public method
|
247
|
+
# only owned by this class. Be careful to not to expose more than intended!
|
248
|
+
def remote_call name, args
|
249
|
+
m = public_method(name)
|
250
|
+
if m && m.owner == self.class
|
251
|
+
m.call(*args)
|
252
|
+
else
|
253
|
+
raise NoMethodError, "method #{name} is not found"
|
254
|
+
end
|
255
|
+
rescue NameError
|
256
|
+
raise NoMethodError, "method #{name} is not found"
|
257
|
+
end
|
258
|
+
|
215
259
|
end
|
216
260
|
|
217
261
|
# Intervace to the remote provider via Farcall protocols. Works the same as if the object
|
@@ -228,7 +272,7 @@ module Farcall
|
|
228
272
|
# Create interface connected to some endpoint ar transpost.
|
229
273
|
#
|
230
274
|
# Please remember that Farcall::Transport instance could be used with only
|
231
|
-
# one
|
275
|
+
# one connected object, unlike Farcall::Endpoint, which could be connected to several
|
232
276
|
# consumers.
|
233
277
|
#
|
234
278
|
# @param [Farcall::Endpoint|Farcall::Transport] arg either endpoint or a transport
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
# :nodoc:
|
4
|
+
class MontorLock
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@condition = ConditionVariable.new
|
8
|
+
@mutex = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def notify
|
12
|
+
@mutex.synchronize {
|
13
|
+
@condition.signal
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait
|
18
|
+
@mutex.synchronize {
|
19
|
+
@condition.wait(@mutex)
|
20
|
+
yield if block_given?
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
# :nodoc:
|
27
|
+
class Semaphore
|
28
|
+
|
29
|
+
def initialize state_set=false
|
30
|
+
@monitor = MontorLock.new
|
31
|
+
@state_set = state_set
|
32
|
+
end
|
33
|
+
|
34
|
+
def set?
|
35
|
+
@state_set
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear?
|
39
|
+
!set?
|
40
|
+
end
|
41
|
+
|
42
|
+
def wait state
|
43
|
+
while @state_set != state do
|
44
|
+
@monitor.wait
|
45
|
+
end
|
46
|
+
@state_set
|
47
|
+
end
|
48
|
+
|
49
|
+
def wait_set
|
50
|
+
wait true
|
51
|
+
end
|
52
|
+
|
53
|
+
def wait_clear
|
54
|
+
wait false
|
55
|
+
end
|
56
|
+
|
57
|
+
def wait_change &block
|
58
|
+
@monitor.wait {
|
59
|
+
block.call(@state_set) if block
|
60
|
+
}
|
61
|
+
@state_set
|
62
|
+
end
|
63
|
+
|
64
|
+
def set new_state=true
|
65
|
+
if @state_set != new_state
|
66
|
+
@state_set = new_state
|
67
|
+
@monitor.notify
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear
|
72
|
+
set false
|
73
|
+
end
|
74
|
+
end
|
data/lib/farcall/transport.rb
CHANGED
@@ -46,7 +46,9 @@ module Farcall
|
|
46
46
|
end
|
47
47
|
|
48
48
|
# Tansport must call this process on each incoming hash
|
49
|
-
# passing it as the only parameter, e.g. self.on_data_received(hash)
|
49
|
+
# passing it as the only parameter, e.g. self.on_data_received.call(hash)
|
50
|
+
# Common trick is to start inner event loop on on_data_recieved=, don't forget
|
51
|
+
# to call super first.
|
50
52
|
attr_accessor :on_data_received, :on_abort, :on_close
|
51
53
|
|
52
54
|
# Utility function. Calls the provided block on data reception. Resets the
|
data/lib/farcall/version.rb
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'farcall'
|
2
|
+
require 'websocket-client-simple'
|
3
|
+
require_relative './monitor_lock'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Farcall
|
7
|
+
# Websocket client transport using JSON encodeing. Works with ruby threads, pure ruby, runs
|
8
|
+
# everywhere. Use if like any thoer Farcall::Transport, for example:
|
9
|
+
#
|
10
|
+
# in your Gemfile
|
11
|
+
#
|
12
|
+
# gem 'websocket-client-simple'
|
13
|
+
#
|
14
|
+
# in the code
|
15
|
+
#
|
16
|
+
# wst = Farcall::WebsocketJsonClientTransport.new 'ws://icodici.com:8080/test'
|
17
|
+
# i = Farcall::Interface.new transport: wst
|
18
|
+
# result = i.authenticate(login, password) # remote call via interface...
|
19
|
+
#
|
20
|
+
class WebsocketJsonClientTransport < Farcall::Transport
|
21
|
+
|
22
|
+
# Create transport connected to the specified websocket url. Constructor blocks
|
23
|
+
# until connected, or raise error if connection can't be established. Transport uses
|
24
|
+
# JSON encodgin over standard websocket protocol.
|
25
|
+
def initialize ws_url
|
26
|
+
# The stranges bug around in the WebSocket::Client (actually in his eventemitter)
|
27
|
+
me = self
|
28
|
+
|
29
|
+
is_open = Semaphore.new
|
30
|
+
@ws = WebSocket::Client::Simple.connect(ws_url)
|
31
|
+
|
32
|
+
@ws.on(:open) {
|
33
|
+
# if me != self
|
34
|
+
# puts "\n\n\nSelf is set to wrong in the callback in #{RUBY_VERSION}\n\n\n"
|
35
|
+
# end
|
36
|
+
# puts "client is open"
|
37
|
+
is_open.set
|
38
|
+
}
|
39
|
+
|
40
|
+
@ws.on(:message) { |m|
|
41
|
+
# puts "ws client received #{JSON.parse m.data}"
|
42
|
+
me.on_data_received and me.on_data_received.call(JSON.parse m.data)
|
43
|
+
# puts "and sent"
|
44
|
+
}
|
45
|
+
@ws.on(:close) { close }
|
46
|
+
is_open.wait_set
|
47
|
+
end
|
48
|
+
|
49
|
+
# :nodoc:
|
50
|
+
def send_data data
|
51
|
+
@ws.send JSON[data]
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/farcall.rb
CHANGED
data/spec/endpoint_spec.rb
CHANGED
@@ -11,8 +11,35 @@ class TestProvider < Farcall::Provider
|
|
11
11
|
@a, @b = a, b
|
12
12
|
return "Foo: #{a+b}, #{optional}"
|
13
13
|
end
|
14
|
+
|
15
|
+
def self.doncallpublic
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_hash
|
20
|
+
{ 'foo' => 'bar', 'bardd' => 'buzz', 'last' => 'item', 'bar' => 'test'}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def dontcall
|
26
|
+
|
27
|
+
end
|
14
28
|
end
|
15
29
|
|
30
|
+
class StringProvider < Farcall::Provider
|
31
|
+
def initialize(str)
|
32
|
+
@str = str
|
33
|
+
end
|
34
|
+
|
35
|
+
def provide_hash
|
36
|
+
{ 'bar' => 'test', 'foo' => 'bar'}
|
37
|
+
end
|
38
|
+
|
39
|
+
def value
|
40
|
+
@str
|
41
|
+
end
|
42
|
+
end
|
16
43
|
|
17
44
|
describe 'endpoint' do
|
18
45
|
include Farcall
|
@@ -38,14 +65,20 @@ describe 'endpoint' do
|
|
38
65
|
i.a.should == 5
|
39
66
|
i.b.should == 6
|
40
67
|
|
41
|
-
ib.split.should == ['Hello', 'world']
|
68
|
+
# ib.split.should == ['Hello', 'world']
|
69
|
+
|
70
|
+
expect(-> { i.dontcall() }).to raise_error Farcall::RemoteError, /NoMethodError/
|
71
|
+
expect(-> { i.sleep() }).to raise_error Farcall::RemoteError, /NoMethodError/
|
72
|
+
expect(-> { i.abort() }).to raise_error Farcall::RemoteError, /NoMethodError/
|
73
|
+
expect(-> { i.doncallpublic() }).to raise_error Farcall::RemoteError, /NoMethodError/
|
74
|
+
expect(-> { i.initialize(1) }).to raise_error Farcall::RemoteError, /NoMethodError/
|
42
75
|
end
|
43
76
|
|
44
77
|
def check_protocol format
|
45
78
|
s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
|
46
79
|
|
47
80
|
tp = TestProvider.new socket: s1, format: format
|
48
|
-
i = Farcall::Interface.new socket: s2, format: format, provider: "
|
81
|
+
i = Farcall::Interface.new socket: s2, format: format, provider: StringProvider.new("bar")
|
49
82
|
|
50
83
|
expect(-> { i.foo() }).to raise_error Farcall::RemoteError
|
51
84
|
|
@@ -55,14 +88,18 @@ describe 'endpoint' do
|
|
55
88
|
i.a.should == 5
|
56
89
|
i.b.should == 6
|
57
90
|
|
58
|
-
|
91
|
+
i.get_hash.foo.should == 'bar'
|
92
|
+
i.get_hash.bar.should == 'test'
|
93
|
+
tp.far_interface.value.should == 'bar'
|
94
|
+
tp.far_interface.provide_hash.bar.should == 'test'
|
95
|
+
tp.far_interface.provide_hash.foo.should == 'bar'
|
59
96
|
end
|
60
97
|
|
61
98
|
it 'should connect json via shortcut' do
|
62
99
|
check_protocol :json
|
63
100
|
end
|
64
101
|
|
65
|
-
it '
|
102
|
+
it 'boss connect boss via shortcut' do
|
66
103
|
check_protocol :boss
|
67
104
|
end
|
68
105
|
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
def standard_check_wsclient(cnt1, r1, r2, r3)
|
5
|
+
r1 = Hashie::Mash.new(r1)
|
6
|
+
r2 = Hashie::Mash.new(r2)
|
7
|
+
|
8
|
+
r1.kwargs.hello.should == 'world'
|
9
|
+
r1.superpong.should == [1, 2, 3]
|
10
|
+
r2.kwargs.hello.should == 'world'
|
11
|
+
r2.superpong.should == [1, 2, 10]
|
12
|
+
|
13
|
+
cnt1.should == 2
|
14
|
+
end
|
15
|
+
|
16
|
+
def standard_check(cnt1, r1, r2, r3)
|
17
|
+
r1.error.should == nil
|
18
|
+
r1.result.kwargs.hello.should == 'world'
|
19
|
+
r1.result.superpong.should == [1, 2, 3]
|
20
|
+
r2.error.should == nil
|
21
|
+
r2.result.kwargs.hello.should == 'world'
|
22
|
+
r2.result.superpong.should == [1, 2, 10]
|
23
|
+
r3.error[:class].should == 'RuntimeError'
|
24
|
+
r3.error.text.should == 'test error'
|
25
|
+
cnt1.should == 3
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def setup_endpoints
|
30
|
+
c1, c2, c3, c4 = 4.times.map { EM::Channel.new }
|
31
|
+
|
32
|
+
@e1 = EmFarcall::Endpoint.new c1, c2
|
33
|
+
@e2 = EmFarcall::Endpoint.new c3, c4
|
34
|
+
|
35
|
+
c2.subscribe { |x| c3 << x }
|
36
|
+
c4.subscribe { |x| c1 << x }
|
37
|
+
|
38
|
+
@e2.on :superping do |*args, **kwargs|
|
39
|
+
if kwargs[:need_error]
|
40
|
+
raise 'test error'
|
41
|
+
end
|
42
|
+
{ superpong: args, kwargs: kwargs }
|
43
|
+
end
|
44
|
+
[@e1, @e2]
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'em_farcall' do
|
48
|
+
|
49
|
+
it 'exchange messages' do
|
50
|
+
r1 = nil
|
51
|
+
r2 = nil
|
52
|
+
r3 = nil
|
53
|
+
cnt1 = 0
|
54
|
+
|
55
|
+
EM.run {
|
56
|
+
e1, e2 = setup_endpoints
|
57
|
+
e1.call 'superping', 1, 2, 3, hello: 'world' do |r|
|
58
|
+
r1 = r
|
59
|
+
cnt1 += 1
|
60
|
+
end
|
61
|
+
|
62
|
+
e1.call 'superping', 1, 2, 10, hello: 'world' do |r|
|
63
|
+
r2 = r
|
64
|
+
cnt1 += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
e1.call 'superping', 1, 2, 10, need_error: true, hello: 'world' do |r|
|
68
|
+
r3 = r
|
69
|
+
cnt1 += 1
|
70
|
+
EM.stop
|
71
|
+
end
|
72
|
+
|
73
|
+
EM.add_timer(4) {
|
74
|
+
EM.stop
|
75
|
+
}
|
76
|
+
}
|
77
|
+
standard_check(cnt1, r1, r2, r3)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'uses remote interface' do
|
81
|
+
r1 = nil
|
82
|
+
r2 = nil
|
83
|
+
r3 = nil
|
84
|
+
cnt1 = 0
|
85
|
+
|
86
|
+
EM.run {
|
87
|
+
e1, e2 = setup_endpoints
|
88
|
+
i = EmFarcall::Interface.new e1
|
89
|
+
|
90
|
+
i.superping(1, 2, 3, hello: 'world') { |r|
|
91
|
+
r1 = r
|
92
|
+
cnt1 += 1
|
93
|
+
}
|
94
|
+
|
95
|
+
i.superping 1, 2, 10, hello: 'world' do |r|
|
96
|
+
r2 = r
|
97
|
+
cnt1 += 1
|
98
|
+
end
|
99
|
+
|
100
|
+
i.superping 1, 2, 10, need_error: true, hello: 'world' do |r|
|
101
|
+
r3 = r
|
102
|
+
cnt1 += 1
|
103
|
+
EM.stop
|
104
|
+
end
|
105
|
+
|
106
|
+
i.test_not_existing
|
107
|
+
|
108
|
+
EM.add_timer(4) {
|
109
|
+
EM.stop
|
110
|
+
}
|
111
|
+
}
|
112
|
+
standard_check(cnt1, r1, r2, r3)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'runs via websockets' do
|
116
|
+
r1 = nil
|
117
|
+
r2 = nil
|
118
|
+
r3 = nil
|
119
|
+
cnt1 = 0
|
120
|
+
|
121
|
+
EM.run {
|
122
|
+
params = {
|
123
|
+
:host => 'localhost',
|
124
|
+
:port => 8088
|
125
|
+
}
|
126
|
+
e1 = nil
|
127
|
+
e2 = nil
|
128
|
+
|
129
|
+
EM::WebSocket.run(params) do |ws|
|
130
|
+
ws.onopen { |handshake|
|
131
|
+
e2 = EmFarcall::WsServerEndpoint.new ws
|
132
|
+
|
133
|
+
e2.on :superping do |*args, **kwargs|
|
134
|
+
if kwargs[:need_error]
|
135
|
+
raise 'test error'
|
136
|
+
end
|
137
|
+
{ superpong: args, kwargs: kwargs }
|
138
|
+
end
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
EM.defer {
|
144
|
+
t1 = Farcall::WebsocketJsonClientTransport.new 'ws://localhost:8088/test'
|
145
|
+
i = Farcall::Interface.new transport: t1
|
146
|
+
|
147
|
+
r1 = i.superping(1, 2, 3, hello: 'world')
|
148
|
+
cnt1 += 1
|
149
|
+
|
150
|
+
r2 = i.superping 1, 2, 10, hello: 'world'
|
151
|
+
cnt1 += 1
|
152
|
+
|
153
|
+
expect {
|
154
|
+
r3 = i.superping 1, 2, 10, need_error: true, hello: 'world'
|
155
|
+
}.to raise_error(Farcall::RemoteError, "RuntimeError: test error")
|
156
|
+
|
157
|
+
expect {
|
158
|
+
r3 = i.superping_bad 13, 2, 10, hello: 'world'
|
159
|
+
}.to raise_error(Farcall::RemoteError, /NoMethodError/)
|
160
|
+
|
161
|
+
|
162
|
+
EM.stop
|
163
|
+
}
|
164
|
+
EM.add_timer(4) {
|
165
|
+
EM.stop
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
standard_check_wsclient(cnt1, r1, r2, r3)
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
class WsProvider < EmFarcall::Provider
|
174
|
+
def superping *args, **kwargs
|
175
|
+
if kwargs[:need_error]
|
176
|
+
raise 'test error'
|
177
|
+
end
|
178
|
+
{ superpong: args, kwargs: kwargs }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'runs via websockets with provider' do
|
183
|
+
r1 = nil
|
184
|
+
r2 = nil
|
185
|
+
r3 = nil
|
186
|
+
cnt1 = 0
|
187
|
+
|
188
|
+
EM.run {
|
189
|
+
params = {
|
190
|
+
:host => 'localhost',
|
191
|
+
:port => 8088
|
192
|
+
}
|
193
|
+
e1 = nil
|
194
|
+
e2 = nil
|
195
|
+
|
196
|
+
EM::WebSocket.run(params) do |ws|
|
197
|
+
ws.onopen { |handshake|
|
198
|
+
e2 = EmFarcall::WsServerEndpoint.new ws, provider: WsProvider.new
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
EM.defer {
|
204
|
+
t1 = Farcall::WebsocketJsonClientTransport.new 'ws://localhost:8088/test'
|
205
|
+
i = Farcall::Interface.new transport: t1
|
206
|
+
|
207
|
+
r1 = i.superping(1, 2, 3, hello: 'world')
|
208
|
+
cnt1 += 1
|
209
|
+
|
210
|
+
r2 = i.superping 1, 2, 10, hello: 'world'
|
211
|
+
cnt1 += 1
|
212
|
+
|
213
|
+
expect {
|
214
|
+
r3 = i.superping 1, 2, 11, need_error: true, hello: 'world'
|
215
|
+
}.to raise_error(Farcall::RemoteError, "RuntimeError: test error")
|
216
|
+
|
217
|
+
expect {
|
218
|
+
r3 = i.superping_bad 13, 2, 10, need_error: true, hello: 'world'
|
219
|
+
}.to raise_error(Farcall::RemoteError, /NoMethodError/)
|
220
|
+
|
221
|
+
|
222
|
+
EM.stop
|
223
|
+
}
|
224
|
+
EM.add_timer(4) {
|
225
|
+
EM.stop
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
standard_check_wsclient(cnt1, r1, r2, r3)
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: farcall
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sergeych
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hashie
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,20 +66,48 @@ dependencies:
|
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: em-websocket
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: websocket-client-simple
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
55
97
|
- !ruby/object:Gem::Dependency
|
56
98
|
name: boss-protocol
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
58
100
|
requirements:
|
59
101
|
- - ">="
|
60
102
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.4.
|
103
|
+
version: 1.4.3
|
62
104
|
type: :development
|
63
105
|
prerelease: false
|
64
106
|
version_requirements: !ruby/object:Gem::Requirement
|
65
107
|
requirements:
|
66
108
|
- - ">="
|
67
109
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.4.
|
110
|
+
version: 1.4.3
|
69
111
|
description: |2
|
70
112
|
Simple and effective cross-platform RPC protocol. Can work with any transport capable to
|
71
113
|
pass structures (dictionaries, hashes, whatever you name it). Out of the box provides
|
@@ -85,15 +127,19 @@ files:
|
|
85
127
|
- farcall.gemspec
|
86
128
|
- lib/farcall.rb
|
87
129
|
- lib/farcall/boss_transport.rb
|
130
|
+
- lib/farcall/em_farcall.rb
|
131
|
+
- lib/farcall/em_wsserver_endpoint.rb
|
88
132
|
- lib/farcall/endpoint.rb
|
89
133
|
- lib/farcall/json_transport.rb
|
134
|
+
- lib/farcall/monitor_lock.rb
|
90
135
|
- lib/farcall/transport.rb
|
91
136
|
- lib/farcall/version.rb
|
137
|
+
- lib/farcall/wsclient_transport.rb
|
92
138
|
- spec/endpoint_spec.rb
|
93
|
-
- spec/rmi_spec.rb
|
94
139
|
- spec/spec_helper.rb
|
95
140
|
- spec/transports_spec.rb
|
96
|
-
|
141
|
+
- spec/websock_spec.rb
|
142
|
+
homepage: https://github.com/sergeych/farcall
|
97
143
|
licenses:
|
98
144
|
- MIT
|
99
145
|
metadata: {}
|
@@ -113,12 +159,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
159
|
version: '0'
|
114
160
|
requirements: []
|
115
161
|
rubyforge_project:
|
116
|
-
rubygems_version: 2.
|
162
|
+
rubygems_version: 2.5.1
|
117
163
|
signing_key:
|
118
164
|
specification_version: 4
|
119
165
|
summary: Simple, elegant and cross-platofrm RPC protocol
|
120
166
|
test_files:
|
121
167
|
- spec/endpoint_spec.rb
|
122
|
-
- spec/rmi_spec.rb
|
123
168
|
- spec/spec_helper.rb
|
124
169
|
- spec/transports_spec.rb
|
170
|
+
- spec/websock_spec.rb
|