farcall 0.4.1 → 0.4.3
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 +2 -1
- data/lib/farcall.rb +5 -0
- data/lib/farcall/boss_transport.rb +1 -1
- data/lib/farcall/em_farcall.rb +235 -233
- data/lib/farcall/em_wsserver_endpoint.rb +69 -66
- data/lib/farcall/endpoint.rb +30 -19
- data/lib/farcall/transport.rb +22 -4
- data/lib/farcall/version.rb +1 -1
- data/spec/endpoint_spec.rb +21 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19db29955b2928e4a9994958f18117232d817b06
|
4
|
+
data.tar.gz: 1b5f72295699d3bf5c6e0ac391e4f85ca5e2fe41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ce68694ca7fa01a3cf74ed975ce840de16206f3e31be89f438eb8a50bb159d60cf3bf288b899475ddd5d28075167c17291d247b8f11d43ffdb9c77ee6791d71
|
7
|
+
data.tar.gz: 265eac526e4d18307f84fcb23a14edff249c85d4b27289cd783a36b63746f0e73cff7cc7c4b867f4e391c0ab9ca1c478ff21cb6408a97b1d9eaf7e8d68fb6887
|
data/README.md
CHANGED
@@ -157,9 +157,10 @@ it could be easily connected to any evented data source.
|
|
157
157
|
|
158
158
|
## Documentation
|
159
159
|
|
160
|
+
* Gem [online docs](http://www.rubydoc.info/gems/farcall)
|
161
|
+
|
160
162
|
* [Farcall protocol](https://github.com/sergeych/farcall/wiki)
|
161
163
|
|
162
|
-
* Gem [online docs](http://www.rubydoc.info/gems/farcall)
|
163
164
|
|
164
165
|
## Contributing
|
165
166
|
|
data/lib/farcall.rb
CHANGED
@@ -8,9 +8,14 @@ begin
|
|
8
8
|
require 'farcall/boss_transport'
|
9
9
|
rescue LoadError
|
10
10
|
end
|
11
|
+
begin
|
11
12
|
require 'farcall/wsclient_transport'
|
12
13
|
require 'farcall/em_wsserver_endpoint'
|
14
|
+
rescue LoadError
|
15
|
+
$!.to_s =~ /em-websocket/ or raise
|
16
|
+
end
|
13
17
|
|
18
|
+
|
14
19
|
|
15
20
|
module Farcall
|
16
21
|
# Your code goes here...
|
@@ -6,7 +6,7 @@ module Farcall
|
|
6
6
|
class BossTransport < Farcall::Transport
|
7
7
|
include TransportBase
|
8
8
|
|
9
|
-
# Create json transport, see Farcall::
|
9
|
+
# Create json transport, see Farcall::Transport#create for parameters
|
10
10
|
def initialize **params
|
11
11
|
super()
|
12
12
|
setup_streams **params
|
data/lib/farcall/em_farcall.rb
CHANGED
@@ -1,259 +1,261 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require_relative './promise'
|
1
|
+
require 'hashie'
|
2
|
+
require 'eventmachine'
|
3
|
+
require_relative './promise'
|
5
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
|
+
# The eventmachine is not a required dependency, to use EmFarcall place eventmachine _before_
|
12
|
+
# _requiring_ _farcall:_
|
13
|
+
#
|
14
|
+
# require 'eventmachine'
|
15
|
+
# require 'farcall'
|
16
|
+
module EmFarcall
|
17
|
+
|
18
|
+
# Endpoint that run in the reactor thread of the EM. Eventmachine should run by the time of
|
19
|
+
# creation of the endpoint. All the methods can be called from any thread, not only
|
20
|
+
# EM's reactor thread.
|
21
|
+
#
|
6
22
|
# As the eventmachine callback paradigm is completely different from the threaded paradigm
|
7
|
-
# of the Farcall, that runs pretty well under
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# paradigm. The rest is the same.
|
23
|
+
# of the Farcall, that runs pretty well under
|
24
|
+
# JRuby and in multithreaded MRI, we provide
|
25
|
+
# compatible but different endpoint to run under EM.
|
11
26
|
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
27
|
+
# Its main difference is that there is no sync_call, instead, calling remote commands
|
28
|
+
# from the endpoint and/ot interface can provide blocks that are called when the remote
|
29
|
+
# is executed.
|
30
|
+
#
|
31
|
+
# The EM version of the endpoint works with any 2 EM:C:Channels.
|
32
|
+
#
|
33
|
+
class Endpoint
|
34
|
+
|
35
|
+
# Create new endpoint to work with input and output channels
|
17
36
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
|
21
|
-
|
37
|
+
# @param [EM::Channel] input_channel
|
38
|
+
# @param [EM::Channel] output_channel
|
39
|
+
def initialize(input_channel, output_channel, errback=nil, provider: nil)
|
40
|
+
EM.schedule {
|
41
|
+
@input, @output, @errback = input_channel, output_channel, errback
|
42
|
+
@trace = false
|
43
|
+
@in_serial = @out_serial = 0
|
44
|
+
@callbacks = {}
|
45
|
+
@handlers = {}
|
46
|
+
@unnamed_handler = -> (name, *args, **kwargs) {
|
47
|
+
raise NoMethodError, "method does not exist: #{name}"
|
48
|
+
}
|
49
|
+
@input.subscribe { |data|
|
50
|
+
process_input(data)
|
51
|
+
}
|
52
|
+
if provider
|
53
|
+
@provider = provider
|
54
|
+
provider.endpoint = self
|
55
|
+
end
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set or get provider instance. When provider is set, its public methods are called by the remote
|
60
|
+
# and any possible exception are passed back to caller party. You can use any ruby class instance
|
61
|
+
# everything will work, operators, indexes[] and like.
|
62
|
+
attr_accessor :provider
|
63
|
+
|
64
|
+
# Call the remote method with specified name and arguments calling block when done. Returns
|
65
|
+
# immediately a {Farcall::Promise} instance which could be used to control remote procedure
|
66
|
+
# invocation result asynchronously and effective.
|
22
67
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# is
|
68
|
+
# Also, if block is provided, it will be called when the remote will be called and possibly
|
69
|
+
# return some data. It receives single object paramter with two fields: result.error and
|
70
|
+
# result.result. It is also possible to use returned {Farcall::Promise} instance to set
|
71
|
+
# multiple callbacks with ease. Promise callbacks are called _after_ the block.
|
26
72
|
#
|
27
|
-
#
|
73
|
+
# `result.error` is not nil when the remote raised error, then `error[:class]` and
|
74
|
+
# `error.text` are set accordingly.
|
28
75
|
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
if
|
49
|
-
|
50
|
-
|
76
|
+
# if error is nil then result.result receives any return data from the remote method.
|
77
|
+
#
|
78
|
+
# for example:
|
79
|
+
#
|
80
|
+
# endpoint.call( 'some_func', 10, 20) { |done|
|
81
|
+
# if done.error
|
82
|
+
# puts "Remote error class: #{done.error[:class]}: #{done.error.text}"
|
83
|
+
# else
|
84
|
+
# puts "Remote returned #{done.result}"
|
85
|
+
# }
|
86
|
+
#
|
87
|
+
# @param name [String] remote method name
|
88
|
+
# @return [Promise] object that call be used to set multiple handlers on success
|
89
|
+
# or fail event. {Farcall::Promise#success} receives remote return result on
|
90
|
+
# success and {Farcall::Promise#fail} receives error object.
|
91
|
+
def call(name, *args, **kwargs, &block)
|
92
|
+
promise = Farcall::Promise.new
|
93
|
+
EM.schedule {
|
94
|
+
@callbacks[@in_serial] = -> (result) {
|
95
|
+
block.call(result) if block != nil
|
96
|
+
if result.error
|
97
|
+
promise.set_fail result.error
|
98
|
+
else
|
99
|
+
promise.set_success result.result
|
51
100
|
end
|
52
101
|
}
|
102
|
+
send_block cmd: name, args: args, kwargs: kwargs
|
103
|
+
}
|
104
|
+
promise
|
105
|
+
end
|
106
|
+
|
107
|
+
# Close the endpoint
|
108
|
+
def close
|
109
|
+
super
|
110
|
+
end
|
111
|
+
|
112
|
+
# Report error via errback and the endpoint
|
113
|
+
def error text
|
114
|
+
STDERR.puts "farcall ws server error #{text}"
|
115
|
+
EM.schedule {
|
116
|
+
@errback.call(text) if @errback
|
117
|
+
close
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
# Set handler to perform the named command. Block will be called when the remote party calls
|
122
|
+
# with parameters passed from the remote. The block returned value will be passed back to
|
123
|
+
# the caller.
|
124
|
+
#
|
125
|
+
# If the block raises the exception it will be reported to the caller as an error (depending
|
126
|
+
# on it's platofrm, will raise exception on its end or report error)
|
127
|
+
def on(name, &block)
|
128
|
+
@handlers[name.to_s] = block
|
129
|
+
end
|
130
|
+
|
131
|
+
# Process remote command. First parameter passed to the block is the method name, the rest
|
132
|
+
# are optional arguments of the call:
|
133
|
+
#
|
134
|
+
# endpoint.on_command { |name, *args, **kwargs|
|
135
|
+
# if name == 'echo'
|
136
|
+
# { args: args, keyword_args: kwargs }
|
137
|
+
# else
|
138
|
+
# raise "unknown command"
|
139
|
+
# end
|
140
|
+
# }
|
141
|
+
#
|
142
|
+
# raising exceptions from the block cause farcall error to be returned back th the caller.
|
143
|
+
def on_command &block
|
144
|
+
raise "unnamed handler should be present" unless block
|
145
|
+
@unnamed_handler = block
|
146
|
+
end
|
147
|
+
|
148
|
+
# Same as #on_command (compatibilty method)
|
149
|
+
def on_remote_call &block
|
150
|
+
on_command block
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get the Farcall::RemoteInterface connnected to this endpoint. Any subsequent calls with
|
154
|
+
# return the same instance.
|
155
|
+
def remote
|
156
|
+
@remote ||= EmFarcall::Interface.new endpoint: self
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# :nodoc: sends block with correct framing
|
162
|
+
def send_block **data
|
163
|
+
data[:serial] = @out_serial
|
164
|
+
@out_serial += 1
|
165
|
+
@output << data
|
166
|
+
end
|
167
|
+
|
168
|
+
# :nodoc:
|
169
|
+
def execute_command cmd, ref, args, kwargs
|
170
|
+
kwargs = (kwargs || {}).inject({}) {
|
171
|
+
|all, kv|
|
172
|
+
all[kv[0].to_sym] = kv[1]
|
173
|
+
all
|
174
|
+
}
|
175
|
+
args << kwargs if kwargs && !kwargs.empty?
|
176
|
+
result = if proc = @handlers[cmd.to_s]
|
177
|
+
proc.call(*args)
|
178
|
+
elsif @provider
|
179
|
+
provider.send :remote_call, cmd.to_sym, args
|
180
|
+
else
|
181
|
+
@unnamed_handler.call(cmd, args)
|
182
|
+
end
|
183
|
+
send_block ref: ref, result: result
|
184
|
+
|
185
|
+
rescue
|
186
|
+
if @trace
|
187
|
+
puts $!
|
188
|
+
puts $!.backtrace.join("\n")
|
53
189
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
#
|
68
|
-
# `result.error` is not nil when the remote raised error, then `error[:class]` and
|
69
|
-
# `error.text` are set accordingly.
|
70
|
-
#
|
71
|
-
# if error is nil then result.result receives any return data from the remote method.
|
72
|
-
#
|
73
|
-
# for example:
|
74
|
-
#
|
75
|
-
# endpoint.call( 'some_func', 10, 20) { |done|
|
76
|
-
# if done.error
|
77
|
-
# puts "Remote error class: #{done.error[:class]}: #{done.error.text}"
|
78
|
-
# else
|
79
|
-
# puts "Remote returned #{done.result}"
|
80
|
-
# }
|
81
|
-
#
|
82
|
-
# @param [String] name command name
|
83
|
-
# @return [Promise] object that call be used to set multiple handlers on success
|
84
|
-
# or fail event. {Farcall::Promise#succsess} receives remote return result on
|
85
|
-
# success and {Farcall::Promise#fail} receives error object.
|
86
|
-
def call(name, *args, **kwargs, &block)
|
87
|
-
promise = Farcall::Promise.new
|
88
|
-
EM.schedule {
|
89
|
-
@callbacks[@in_serial] = -> (result) {
|
90
|
-
block.call(result) if block != nil
|
91
|
-
if result.error
|
92
|
-
promise.set_fail result.error
|
93
|
-
else
|
94
|
-
promise.set_success result.result
|
95
|
-
end
|
96
|
-
}
|
97
|
-
send_block cmd: name, args: args, kwargs: kwargs
|
98
|
-
}
|
99
|
-
promise
|
100
|
-
end
|
101
|
-
|
102
|
-
# Close the endpoint
|
103
|
-
def close
|
104
|
-
super
|
105
|
-
end
|
106
|
-
|
107
|
-
# Report error via errback and the endpoint
|
108
|
-
def error text
|
109
|
-
STDERR.puts "farcall ws server error #{text}"
|
110
|
-
EM.schedule {
|
111
|
-
@errback.call(text) if @errback
|
112
|
-
close
|
113
|
-
}
|
114
|
-
end
|
115
|
-
|
116
|
-
# Set handler to perform the named command. Block will be called when the remote party calls
|
117
|
-
# with parameters passed from the remote. The block returned value will be passed back to
|
118
|
-
# the caller.
|
119
|
-
#
|
120
|
-
# If the block raises the exception it will be reported to the caller as an error (depending
|
121
|
-
# on it's platofrm, will raise exception on its end or report error)
|
122
|
-
def on(name, &block)
|
123
|
-
@handlers[name.to_s] = block
|
124
|
-
end
|
125
|
-
|
126
|
-
# Process remote command. First parameter passed to the block is the method name, the rest
|
127
|
-
# are optional arguments of the call:
|
128
|
-
#
|
129
|
-
# endpoint.on_command { |name, *args, **kwargs|
|
130
|
-
# if name == 'echo'
|
131
|
-
# { args: args, keyword_args: kwargs }
|
132
|
-
# else
|
133
|
-
# raise "unknown command"
|
134
|
-
# end
|
135
|
-
# }
|
136
|
-
#
|
137
|
-
# raising exceptions from the block cause farcall error to be returned back th the caller.
|
138
|
-
def on_command &block
|
139
|
-
raise "unnamed handler should be present" unless block
|
140
|
-
@unnamed_handler = block
|
141
|
-
end
|
142
|
-
|
143
|
-
# Same as #on_command (compatibilty method)
|
144
|
-
def on_remote_call &block
|
145
|
-
on_command block
|
146
|
-
end
|
147
|
-
|
148
|
-
# Get the Farcall::RemoteInterface connnected to this endpoint. Any subsequent calls with
|
149
|
-
# return the same instance.
|
150
|
-
def remote
|
151
|
-
@remote ||= EmFarcall::Interface.new endpoint: self
|
152
|
-
end
|
153
|
-
|
154
|
-
private
|
155
|
-
|
156
|
-
# :nodoc: sends block with correct framing
|
157
|
-
def send_block **data
|
158
|
-
data[:serial] = @out_serial
|
159
|
-
@out_serial += 1
|
160
|
-
@output << data
|
161
|
-
end
|
162
|
-
|
163
|
-
# :nodoc:
|
164
|
-
def execute_command cmd, ref, args, kwargs
|
165
|
-
kwargs = (kwargs || {}).inject({}) {
|
166
|
-
|all, kv|
|
167
|
-
all[kv[0].to_sym] = kv[1]
|
168
|
-
all
|
169
|
-
}
|
170
|
-
args << kwargs if kwargs && !kwargs.empty?
|
171
|
-
result = if proc = @handlers[cmd.to_s]
|
172
|
-
proc.call(*args)
|
173
|
-
elsif @provider
|
174
|
-
provider.send :remote_call, cmd.to_sym, args
|
175
|
-
else
|
176
|
-
@unnamed_handler.call(cmd, args)
|
177
|
-
end
|
178
|
-
send_block ref: ref, result: result
|
179
|
-
|
180
|
-
rescue
|
181
|
-
if @trace
|
182
|
-
puts $!
|
183
|
-
puts $!.backtrace.join("\n")
|
184
|
-
end
|
185
|
-
send_block ref: ref, error: { class: $!.class.name, text: $!.to_s }
|
186
|
-
end
|
187
|
-
|
188
|
-
# :nodoc: important that this method is called from reactor thread only
|
189
|
-
def process_input data
|
190
|
-
# To be free from :keys and 'keys'
|
191
|
-
data = Hashie::Mash.new(data) unless data.is_a?(Hashie::Mash)
|
192
|
-
if data.serial != @in_serial
|
193
|
-
error "framing error (wrong serial:)"
|
190
|
+
send_block ref: ref, error: { class: $!.class.name, text: $!.to_s }
|
191
|
+
end
|
192
|
+
|
193
|
+
# :nodoc: important that this method is called from reactor thread only
|
194
|
+
def process_input data
|
195
|
+
# To be free from :keys and 'keys'
|
196
|
+
data = Hashie::Mash.new(data) unless data.is_a?(Hashie::Mash)
|
197
|
+
if data.serial != @in_serial
|
198
|
+
error "framing error (wrong serial:)"
|
199
|
+
else
|
200
|
+
@in_serial += 1
|
201
|
+
if (cmd = data.cmd) != nil
|
202
|
+
execute_command(cmd, data.serial, data.args || [], data.kwargs || {})
|
194
203
|
else
|
195
|
-
|
196
|
-
if
|
197
|
-
|
198
|
-
|
199
|
-
ref = data.ref
|
200
|
-
if ref
|
201
|
-
if (block = @callbacks.delete(ref)) != nil
|
202
|
-
block.call(Hashie::Mash.new(result: data.result, error: data.error))
|
203
|
-
end
|
204
|
-
else
|
205
|
-
error "framing error: no ref in block #{data.inspect}"
|
204
|
+
ref = data.ref
|
205
|
+
if ref
|
206
|
+
if (block = @callbacks.delete(ref)) != nil
|
207
|
+
block.call(Hashie::Mash.new(result: data.result, error: data.error))
|
206
208
|
end
|
209
|
+
else
|
210
|
+
error "framing error: no ref in block #{data.inspect}"
|
207
211
|
end
|
208
212
|
end
|
209
213
|
end
|
210
214
|
end
|
211
|
-
|
212
|
-
|
213
|
-
|
215
|
+
end
|
216
|
+
|
217
|
+
# Interface to the remote provider via Farcall protocols. Works the same as if the object
|
218
|
+
# is local and yields block in return, unlike Farcall::Interface that blocks
|
219
|
+
#
|
220
|
+
# RemoteInterface transparently creates methods as you call them to speedup subsequent
|
221
|
+
# calls.
|
222
|
+
#
|
223
|
+
class Interface
|
224
|
+
|
225
|
+
# Create interface connected to some endpoint ar transpost.
|
214
226
|
#
|
215
|
-
#
|
216
|
-
#
|
227
|
+
# Please remember that Farcall::Transport instance could be used with only
|
228
|
+
# one connected object, unlike Farcall::Endpoint, which could be connected to several
|
229
|
+
# consumers.
|
217
230
|
#
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
# @param [Farcall::Endpoint|Farcall::Transport] arg either endpoint or a transport
|
227
|
-
# to connect interface to
|
228
|
-
def initialize(endpoint)
|
229
|
-
@endpoint = endpoint
|
230
|
-
end
|
231
|
-
|
232
|
-
def method_missing(method_name, *arguments, **kw_arguments, &block)
|
233
|
-
instance_eval <<-End
|
231
|
+
# @param [Farcall::Endpoint|Farcall::Transport] arg either endpoint or a transport
|
232
|
+
# to connect interface to
|
233
|
+
def initialize(endpoint)
|
234
|
+
@endpoint = endpoint
|
235
|
+
end
|
236
|
+
|
237
|
+
def method_missing(method_name, *arguments, **kw_arguments, &block)
|
238
|
+
instance_eval <<-End
|
234
239
|
def #{method_name} *arguments, **kw_arguments, &block
|
235
240
|
@endpoint.call '#{method_name}', *arguments, **kw_arguments, &block
|
236
241
|
end
|
237
|
-
|
238
|
-
|
239
|
-
end
|
240
|
-
|
241
|
-
def respond_to_missing?(method_name, include_private = false)
|
242
|
-
true
|
243
|
-
end
|
242
|
+
End
|
243
|
+
@endpoint.call method_name, *arguments, **kw_arguments, &block
|
244
244
|
end
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
245
|
+
|
246
|
+
def respond_to_missing?(method_name, include_private = false)
|
247
|
+
true
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
class Provider < Farcall::Provider
|
252
|
+
|
253
|
+
attr_accessor :endpoint
|
254
|
+
|
255
|
+
def far_interface
|
256
|
+
endpoint.remote
|
254
257
|
end
|
258
|
+
|
255
259
|
end
|
256
|
-
rescue LoadError
|
257
|
-
$!.to_s =~ /eventmachine/ or raise
|
258
260
|
end
|
259
261
|
|
@@ -1,73 +1,76 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require_relative './em_farcall'
|
1
|
+
require 'em-websocket'
|
2
|
+
require 'eventmachine'
|
3
|
+
require_relative './em_farcall'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
5
|
+
module EmFarcall
|
6
|
+
|
7
|
+
# Farcall websocket client, based on the websocket-client-simple gem. We do not put it into
|
8
|
+
# dependencies as it is not always needed, so add to your Gemfile first:
|
9
|
+
#
|
10
|
+
# gem 'websocket-client-simple'
|
11
|
+
#
|
12
|
+
# and do not forget to require eventmachine before requiring the farcall. Now you can use it
|
13
|
+
# with EM:
|
14
|
+
#
|
15
|
+
# require 'eventmachine'
|
16
|
+
# require 'farcall'
|
17
|
+
#
|
18
|
+
# # ....
|
19
|
+
#
|
20
|
+
# endpoint = nil
|
21
|
+
#
|
22
|
+
# EM::WebSocket.run(params) do |ws|
|
23
|
+
# ws.onopen { |handshake|
|
24
|
+
# # Check handshake.path, handshake query
|
25
|
+
# # for example to select the provider, then connect Farcall to websocket:
|
26
|
+
# endpoint = EmFarcall::WsServerEndpoint.new ws, provider: WsProvider.new
|
27
|
+
# }
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# now we can use it as usual: remote can call provder method, and we can call remote:
|
31
|
+
#
|
32
|
+
# endpoint.remote.do_something( times: 4) { |result|
|
33
|
+
# }
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# We do not include it into gem dependencies as it uses EventMachine
|
37
|
+
# which is not needed under JRuby and weight alot (the resto of Farcall plays well with jruby
|
38
|
+
# and MRI threads)
|
39
|
+
#
|
40
|
+
# Due to event-driven nature of eventmachine, WsServerEndpoint uses special version of
|
41
|
+
# {EmFarcall::Endpoint} and {EmFarcall::WsProvider} which are code compatible with regular
|
42
|
+
# farcall classes except for the callback-style calls where appropriate.
|
43
|
+
class WsServerEndpoint < EmFarcall::Endpoint
|
44
|
+
|
45
|
+
# Create endpoint with the already opened websocket instance. Note that all the handshake
|
46
|
+
# should be done prior to construct endpoint (e.g. you may want to have different endpoints
|
47
|
+
# for different paths and arguments)
|
23
48
|
#
|
24
|
-
#
|
49
|
+
# See {EmFarcall::Endpoint} for methods to call remote interface and process remote requests.
|
25
50
|
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
51
|
+
# @param [EM::WebSocket] websocket socket in open state (handshake should be passed)
|
52
|
+
def initialize websocket, **kwargs
|
53
|
+
@input = EM::Channel.new
|
54
|
+
@output = EM::Channel.new
|
55
|
+
super(@input, @output, **kwargs)
|
56
|
+
|
57
|
+
websocket.onmessage { |data|
|
58
|
+
@input << unpack(data)
|
59
|
+
}
|
60
|
+
@output.subscribe { |data|
|
61
|
+
websocket.send(pack data)
|
62
|
+
}
|
67
63
|
end
|
68
|
-
|
64
|
+
|
65
|
+
def unpack data
|
66
|
+
JSON.parse data
|
67
|
+
end
|
68
|
+
|
69
|
+
def pack data
|
70
|
+
JSON[data]
|
71
|
+
end
|
72
|
+
|
69
73
|
end
|
70
|
-
|
71
|
-
$!.to_s =~ /em-websocket/ or raise
|
74
|
+
|
72
75
|
end
|
73
76
|
|
data/lib/farcall/endpoint.rb
CHANGED
@@ -31,8 +31,8 @@ module Farcall
|
|
31
31
|
|
32
32
|
init_proc.call(self) if init_proc
|
33
33
|
|
34
|
+
# @!visibility private
|
34
35
|
def push_input data
|
35
|
-
p 'me -- pusj!', data
|
36
36
|
@in_buffer << data
|
37
37
|
drain
|
38
38
|
end
|
@@ -61,7 +61,7 @@ module Farcall
|
|
61
61
|
@close_handler = block
|
62
62
|
end
|
63
63
|
|
64
|
-
#
|
64
|
+
# @!visibility private
|
65
65
|
def abort reason, exception = nil
|
66
66
|
puts "*** Abort: reason #{reason || exception.to_s}"
|
67
67
|
@abort_hadnler and @abort_hadnler.call reason, exception
|
@@ -78,13 +78,18 @@ module Farcall
|
|
78
78
|
@close_handler and @close_handler.call
|
79
79
|
end
|
80
80
|
|
81
|
-
# Call remote party
|
82
|
-
#
|
83
|
-
# or result itself are instances of th Hashie::Mash. Error could be nil or
|
84
|
-
# {'class' =>, 'text' => } Hashie::Mash hash. result is always nil if error is presented.
|
81
|
+
# Call the remote party, non blocking, returns {Farcall::Promise} instance to handle the remote
|
82
|
+
# return valy asynchronously (recommended way).
|
85
83
|
#
|
86
|
-
#
|
87
|
-
#
|
84
|
+
# Optionally the block could be provided
|
85
|
+
# that takes |error, result| parameters. Error must be nil or
|
86
|
+
#
|
87
|
+
# Hashie::Mash.new({'class' =>, 'text' => text [, data: {some_data}] })
|
88
|
+
#
|
89
|
+
# if error is presented, the result is always the nil.
|
90
|
+
#
|
91
|
+
# Usually, using {#remote} which returns
|
92
|
+
# {Farcall::Interface} is more effective rather than this low-level method.
|
88
93
|
#
|
89
94
|
# The returned {Farcall::Promise} instance let add any number of callbacks on commend execution,
|
90
95
|
# success or failure.
|
@@ -140,7 +145,7 @@ module Farcall
|
|
140
145
|
same_thread or resource.wait(mutex)
|
141
146
|
}
|
142
147
|
if error
|
143
|
-
raise Farcall::RemoteError.new(error['class'], error['text'])
|
148
|
+
raise Farcall::RemoteError.new(error['class'], error['text'], error['data'])
|
144
149
|
end
|
145
150
|
result
|
146
151
|
end
|
@@ -171,8 +176,9 @@ module Farcall
|
|
171
176
|
end
|
172
177
|
|
173
178
|
|
174
|
-
# Get the Farcall::
|
179
|
+
# Get the {Farcall::Interface} connnected to this endpoint. Any subsequent calls with
|
175
180
|
# return the same instance.
|
181
|
+
# @return [Farcall::Interface] the remote interface instance
|
176
182
|
def remote
|
177
183
|
@remote ||= Farcall::Interface.new endpoint: self
|
178
184
|
end
|
@@ -224,8 +230,9 @@ module Farcall
|
|
224
230
|
rescue Exception => e
|
225
231
|
# puts e
|
226
232
|
# puts e.backtrace.join("\n")
|
227
|
-
|
228
|
-
|
233
|
+
error_data = { 'class' => e.class.name, 'text' => e.to_s }
|
234
|
+
e.respond_to?(:data) and error_data[:data] = e.data
|
235
|
+
_send ref: serial, error: error_data
|
229
236
|
end
|
230
237
|
|
231
238
|
when ref
|
@@ -300,14 +307,15 @@ module Farcall
|
|
300
307
|
|
301
308
|
end
|
302
309
|
|
303
|
-
#
|
304
|
-
#
|
305
|
-
#
|
306
|
-
# RemoteInterface transparently creates methods as you call them to speedup subsequent
|
307
|
-
# calls.
|
310
|
+
# Interface to the remote provider via Farcall protocols. Works the same as the normal, local
|
311
|
+
# object, but slower. This interface is returned by {Farcall::Endpoint#remote}. The Interface
|
312
|
+
# transparently creates methods as you call them to speed up subsequent calls.
|
308
313
|
#
|
309
314
|
# There is no way to check that the remote responds to some method other than call it and
|
310
|
-
# catch the exception
|
315
|
+
# catch the exception.
|
316
|
+
#
|
317
|
+
# See {Farcall::RemoteError} for more information on passing errors.
|
318
|
+
#
|
311
319
|
#
|
312
320
|
class Interface
|
313
321
|
|
@@ -317,7 +325,7 @@ module Farcall
|
|
317
325
|
# one connected object, unlike Farcall::Endpoint, which could be connected to several
|
318
326
|
# consumers.
|
319
327
|
#
|
320
|
-
# @param [Farcall::Endpoint|Farcall::Transport]
|
328
|
+
# @param arg [Farcall::Endpoint|Farcall::Transport] either endpoint or a transport
|
321
329
|
# to connect interface to
|
322
330
|
def initialize endpoint: nil, transport: nil, provider: nil, **params
|
323
331
|
@endpoint = if endpoint
|
@@ -328,8 +336,10 @@ module Farcall
|
|
328
336
|
provider and @endpoint.provider = provider
|
329
337
|
end
|
330
338
|
|
339
|
+
# the {Farcall::Endpoint} to which this interface is connected.
|
331
340
|
attr :endpoint
|
332
341
|
|
342
|
+
# used internally to synthesize the proxy method.
|
333
343
|
def method_missing(method_name, *arguments, **kw_arguments, &block)
|
334
344
|
instance_eval <<-End
|
335
345
|
def #{method_name} *arguments, **kw_arguments
|
@@ -339,6 +349,7 @@ module Farcall
|
|
339
349
|
@endpoint.sync_call method_name, *arguments, **kw_arguments
|
340
350
|
end
|
341
351
|
|
352
|
+
# used internally to synthesize the proxy method.
|
342
353
|
def respond_to_missing?(method_name, include_private = false)
|
343
354
|
true
|
344
355
|
end
|
data/lib/farcall/transport.rb
CHANGED
@@ -4,12 +4,30 @@ module Farcall
|
|
4
4
|
class Error < StandardError
|
5
5
|
end
|
6
6
|
|
7
|
-
# The error occured while executin remote method
|
7
|
+
# The error occured while executin remote method. Inf an exception is raused while executing remote
|
8
|
+
# method, it will be passed back and raised in the caller thread with an instance of this class.
|
9
|
+
#
|
10
|
+
# The remote can throw regular or extended errors.
|
11
|
+
# Extended errors can pass any data to the other end, which will be available as {#data}.
|
12
|
+
#
|
13
|
+
# To carry error data just raise any exception which respnd_to(:data), which should return hash
|
14
|
+
# with any application specific error data, as in this sample error class:
|
15
|
+
#
|
16
|
+
# class MyError < StandardError
|
17
|
+
# def data
|
18
|
+
# { foo: bar }
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
8
22
|
class RemoteError < Error
|
23
|
+
# The class of the remote error. Usually string name of the class or any other useful string
|
24
|
+
# identifier.
|
9
25
|
attr :remote_class
|
26
|
+
# data passed by the remote error or an empty hash
|
27
|
+
attr :data
|
10
28
|
|
11
|
-
def initialize remote_class, text
|
12
|
-
@remote_class = remote_class
|
29
|
+
def initialize remote_class, text, data={}
|
30
|
+
@remote_class, @data = remote_class, data
|
13
31
|
super "#{remote_class}: #{text}"
|
14
32
|
end
|
15
33
|
end
|
@@ -27,7 +45,7 @@ module Farcall
|
|
27
45
|
#
|
28
46
|
# - socket: connect transport to some socket (should be connected)
|
29
47
|
#
|
30
|
-
# - input and
|
48
|
+
# - input and output: two stream-like objects which support read(length) and write(data)
|
31
49
|
# parameters
|
32
50
|
#
|
33
51
|
def self.create format: :json, **params
|
data/lib/farcall/version.rb
CHANGED
data/spec/endpoint_spec.rb
CHANGED
@@ -44,7 +44,7 @@ end
|
|
44
44
|
describe 'endpoint' do
|
45
45
|
include Farcall
|
46
46
|
|
47
|
-
it 'runs
|
47
|
+
it 'runs commands' do
|
48
48
|
tc = Farcall::LocalConnection.new
|
49
49
|
|
50
50
|
ea = Farcall::Endpoint.new tc.a
|
@@ -56,6 +56,26 @@ describe 'endpoint' do
|
|
56
56
|
rs = eb.remote.command_one("uno", 'due', tre: 3)
|
57
57
|
rs.return_one[0].should == ['uno', 'due']
|
58
58
|
rs.return_one[1].should == { 'tre' => 3}
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'return extended errors' do
|
62
|
+
class ExtendedError < StandardError
|
63
|
+
def data
|
64
|
+
{ extended: 'information'}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
tc = Farcall::LocalConnection.new
|
69
|
+
|
70
|
+
ea = Farcall::Endpoint.new tc.a
|
71
|
+
eb = Farcall::Endpoint.new tc.b
|
72
|
+
|
73
|
+
ea.on('emit_extended_error') { |args, kwargs|
|
74
|
+
raise ExtendedError, "SOME TEXT"
|
75
|
+
}
|
76
|
+
expect( ->{eb.remote.emit_extended_error("uno", 'due', tre: 3)} ).to raise_error { |error|
|
77
|
+
error.data['extended'].should == 'information'
|
78
|
+
}
|
59
79
|
|
60
80
|
end
|
61
81
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: farcall
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sergeych
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|