farcall 0.4.1 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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