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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f05a56fd2addc879cc18a3337bc554cfcb32b5fe
4
- data.tar.gz: e106170eb6ea752a5e3e6a6e625561ab68e2e972
3
+ metadata.gz: 19db29955b2928e4a9994958f18117232d817b06
4
+ data.tar.gz: 1b5f72295699d3bf5c6e0ac391e4f85ca5e2fe41
5
5
  SHA512:
6
- metadata.gz: e97aee1efa98146211c2fd40756b53015fb0257dbc7c5a3b7d114183db74e1746c1f63139c07b8e6d561f838654d0f3a5823cea502fe9cb25a305921d2244a15
7
- data.tar.gz: ef66b3d81c8ffdf23209e28942811e3fac9496f6b8d8d227a8ab855bd76b02a8be2c8da0c93920c1794d4642f18c430d9aac8365872af2d3f3c5cb2978a6e048
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
 
@@ -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::Transpor#create for parameters
9
+ # Create json transport, see Farcall::Transport#create for parameters
10
10
  def initialize **params
11
11
  super()
12
12
  setup_streams **params
@@ -1,259 +1,261 @@
1
- begin
2
- require 'hashie'
3
- require 'eventmachine'
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 JRuby and in multithreaded MRI, we provide
8
- # compatible but different implementations: {EmFarcall::Endpoint}, {EmFarcall::Interface}
9
- # and {EmFarcall::Provider}. Changes to adapt these are minimal except of the callback
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
- module EmFarcall
13
-
14
- # Endpoint that run in the reactor thread of the EM. Eventmachine should rin by the time of
15
- # creation of the endpoint. All the methods can be called from any thread, not only
16
- # EM's reactor thread.
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
- # As the eventmachine callback paradigm is completely different from the threaded paradigm
19
- # of the Farcall, that runs pretty well under
20
- # JRuby and in multithreaded MRI, we provide
21
- # compatible but different endpoint to run under EM.
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
- # Its main difference is that there is no sync_call, instead, calling remote commands
24
- # from the endpoint and/ot interface can provide blocks that are called when the remote
25
- # is executed.
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
- # The EM version of the endpoint works with any 2 EM:C:Channels.
73
+ # `result.error` is not nil when the remote raised error, then `error[:class]` and
74
+ # `error.text` are set accordingly.
28
75
  #
29
- class Endpoint
30
-
31
- # Create new endpoint to work with input and output channels
32
- #
33
- # @param [EM::Channel] input_channel
34
- # @param [EM::Channel] output_channel
35
- def initialize(input_channel, output_channel, errback=nil, provider: nil)
36
- EM.schedule {
37
- @input, @output, @errback = input_channel, output_channel, errback
38
- @trace = false
39
- @in_serial = @out_serial = 0
40
- @callbacks = {}
41
- @handlers = {}
42
- @unnamed_handler = -> (name, *args, **kwargs) {
43
- raise NoMethodError, "method does not exist: #{name}"
44
- }
45
- @input.subscribe { |data|
46
- process_input(data)
47
- }
48
- if provider
49
- @provider = provider
50
- provider.endpoint = self
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
- # Set or get provider instance. When provider is set, its public methods are called by the remote
56
- # and any possible exception are passed back to caller party. You can use any ruby class instance
57
- # everything will work, operators, indexes[] and like.
58
- attr_accessor :provider
59
-
60
- # Call remote with specified name and arguments calling block when done.
61
- # if block is provided, it will be called when the remote will be called and possibly return
62
- # some data.
63
- #
64
- # Block if present receives single object paramter with two fields: `result.error` and
65
- # `result.result`. It is also possible to use returned {Farcall::Promise} instance to set
66
- # multiple callbacks with ease. Promise callbacks are called _after_ the block.
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
- @in_serial += 1
196
- if (cmd = data.cmd) != nil
197
- execute_command(cmd, data.serial, data.args || [], data.kwargs || {})
198
- else
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
- # Interface to the remote provider via Farcall protocols. Works the same as if the object
213
- # is local and yields block in return, unlike Farcall::Interface that blocks
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
- # RemoteInterface transparently creates methods as you call them to speedup subsequent
216
- # calls.
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
- class Interface
219
-
220
- # Create interface connected to some endpoint ar transpost.
221
- #
222
- # Please remember that Farcall::Transport instance could be used with only
223
- # one connected object, unlike Farcall::Endpoint, which could be connected to several
224
- # consumers.
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
- End
238
- @endpoint.call method_name, *arguments, **kw_arguments, &block
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
- class Provider < Farcall::Provider
247
-
248
- attr_accessor :endpoint
249
-
250
- def far_interface
251
- endpoint.remote
252
- end
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
- begin
2
- require 'em-websocket'
3
- require 'eventmachine'
4
- require_relative './em_farcall'
1
+ require 'em-websocket'
2
+ require 'eventmachine'
3
+ require_relative './em_farcall'
5
4
 
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
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
- # now we can use it as usual: remote can call provder method, and we can call remote:
49
+ # See {EmFarcall::Endpoint} for methods to call remote interface and process remote requests.
25
50
  #
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
-
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
- rescue LoadError
71
- $!.to_s =~ /em-websocket/ or raise
74
+
72
75
  end
73
76
 
@@ -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
- # :nodoc:
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. Retruns immediately. When remote party answers, calls the specified block
82
- # if present. The block should take |error, result| parameters. If result's content hashes
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
- # Usually, using Farcall::Endpoint#interface or
87
- # Farcall::RemoteInterface is more effective rather than this low-level method.
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::RemoteInterface connnected to this endpoint. Any subsequent calls with
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
- _send ref: serial, error: { 'class' => e.class.name, 'text' => e.to_s }
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
- # Intervace to the remote provider via Farcall protocols. Works the same as if the object
304
- # would be in local data, but slower :) The same as calling Farcall::Endpoint#interface
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] arg either endpoint or a 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
@@ -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 aoutput: two stream-like objects which support read(length) and write(data)
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
@@ -1,3 +1,3 @@
1
1
  module Farcall
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.3"
3
3
  end
@@ -44,7 +44,7 @@ end
44
44
  describe 'endpoint' do
45
45
  include Farcall
46
46
 
47
- it 'runs on commands' do
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.1
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-01 00:00:00.000000000 Z
11
+ date: 2016-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashie