xrbp 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +103 -5
  3. data/examples/accounts.rb +13 -0
  4. data/examples/autoconnect_timeout.rb +5 -1
  5. data/examples/autorety.rb +7 -0
  6. data/examples/crawl_nodes.rb +32 -0
  7. data/examples/dsl/account.rb +16 -0
  8. data/examples/dsl/ledger.rb +7 -0
  9. data/examples/dsl/ledger_subscribe.rb +18 -0
  10. data/examples/dsl/validators.rb +8 -0
  11. data/examples/gateways.rb +8 -0
  12. data/examples/latest_account.rb +4 -0
  13. data/examples/ledger_multi_subscribe.rb +1 -1
  14. data/examples/ledger_subscribe.rb +2 -2
  15. data/examples/market.rb +13 -0
  16. data/examples/username.rb +12 -0
  17. data/examples/validator.rb +8 -0
  18. data/lib/xrbp.rb +5 -1
  19. data/lib/xrbp/common.rb +10 -0
  20. data/lib/xrbp/core_ext.rb +24 -0
  21. data/lib/xrbp/dsl.rb +25 -0
  22. data/lib/xrbp/dsl/accounts.rb +13 -0
  23. data/lib/xrbp/dsl/ledgers.rb +23 -0
  24. data/lib/xrbp/dsl/validators.rb +10 -0
  25. data/lib/xrbp/dsl/webclient.rb +8 -0
  26. data/lib/xrbp/dsl/websocket.rb +28 -0
  27. data/lib/xrbp/model.rb +4 -0
  28. data/lib/xrbp/model/account.rb +142 -1
  29. data/lib/xrbp/model/base.rb +1 -0
  30. data/lib/xrbp/model/gateway.rb +24 -0
  31. data/lib/xrbp/model/ledger.rb +30 -1
  32. data/lib/xrbp/model/market.rb +52 -0
  33. data/lib/xrbp/model/node.rb +131 -0
  34. data/lib/xrbp/model/parsers/account.rb +44 -0
  35. data/lib/xrbp/model/parsers/gateway.rb +40 -0
  36. data/lib/xrbp/model/parsers/market.rb +28 -0
  37. data/lib/xrbp/model/parsers/node.rb +19 -0
  38. data/lib/xrbp/model/parsers/quote.rb +47 -0
  39. data/lib/xrbp/model/parsers/validator.rb +25 -0
  40. data/lib/xrbp/model/validator.rb +24 -0
  41. data/lib/xrbp/plugins.rb +6 -0
  42. data/lib/xrbp/plugins/base.rb +10 -0
  43. data/lib/xrbp/plugins/has_plugin.rb +45 -0
  44. data/lib/xrbp/plugins/has_result_parsers.rb +27 -0
  45. data/lib/xrbp/plugins/plugin_registry.rb +20 -0
  46. data/lib/xrbp/plugins/result_parser.rb +19 -0
  47. data/lib/xrbp/terminatable.rb +19 -0
  48. data/lib/xrbp/thread_registry.rb +22 -0
  49. data/lib/xrbp/version.rb +1 -1
  50. data/lib/xrbp/webclient.rb +2 -0
  51. data/lib/xrbp/webclient/connection.rb +100 -0
  52. data/lib/xrbp/webclient/plugins.rb +8 -0
  53. data/lib/xrbp/webclient/plugins/autoretry.rb +54 -0
  54. data/lib/xrbp/webclient/plugins/result_parser.rb +23 -0
  55. data/lib/xrbp/websocket/client.rb +85 -24
  56. data/lib/xrbp/websocket/cmds/account_info.rb +4 -0
  57. data/lib/xrbp/websocket/cmds/account_lines.rb +5 -0
  58. data/lib/xrbp/websocket/cmds/account_objects.rb +4 -0
  59. data/lib/xrbp/websocket/cmds/account_offers.rb +5 -0
  60. data/lib/xrbp/websocket/cmds/account_tx.rb +4 -0
  61. data/lib/xrbp/websocket/cmds/book_offers.rb +4 -0
  62. data/lib/xrbp/websocket/cmds/ledger.rb +3 -0
  63. data/lib/xrbp/websocket/cmds/ledger_entry.rb +4 -0
  64. data/lib/xrbp/websocket/cmds/paginated.rb +4 -1
  65. data/lib/xrbp/websocket/cmds/server_info.rb +4 -0
  66. data/lib/xrbp/websocket/cmds/subscribe.rb +4 -0
  67. data/lib/xrbp/websocket/command.rb +11 -0
  68. data/lib/xrbp/websocket/connection.rb +75 -22
  69. data/lib/xrbp/websocket/message.rb +5 -2
  70. data/lib/xrbp/websocket/multi/fallback.rb +2 -4
  71. data/lib/xrbp/websocket/multi/multi_connection.rb +37 -2
  72. data/lib/xrbp/websocket/multi/parallel.rb +2 -4
  73. data/lib/xrbp/websocket/multi/prioritized.rb +2 -4
  74. data/lib/xrbp/websocket/multi/round_robin.rb +4 -0
  75. data/lib/xrbp/websocket/plugins.rb +1 -7
  76. data/lib/xrbp/websocket/plugins/autoconnect.rb +25 -5
  77. data/lib/xrbp/websocket/plugins/command_dispatcher.rb +5 -0
  78. data/lib/xrbp/websocket/plugins/command_paginator.rb +9 -8
  79. data/lib/xrbp/websocket/plugins/connection_timeout.rb +27 -16
  80. data/lib/xrbp/websocket/plugins/message_dispatcher.rb +60 -30
  81. data/lib/xrbp/websocket/plugins/result_parser.rb +19 -19
  82. data/lib/xrbp/websocket/socket.rb +23 -6
  83. metadata +118 -8
  84. data/lib/xrbp/network.rb +0 -6
  85. data/lib/xrbp/network/nodes.rb +0 -8
  86. data/lib/xrbp/websocket/has_plugin.rb +0 -30
@@ -1,8 +1,10 @@
1
1
  module XRBP
2
2
  module WebSocket
3
+ # Raw data which to write to websocket and mechanisms
4
+ # which to track and manage response state.
3
5
  class Message
4
- attr_reader :result, :time
5
- attr_accessor :connection
6
+ attr_reader :result
7
+ attr_accessor :time, :connection
6
8
 
7
9
  def initialize(data)
8
10
  @data = data
@@ -19,6 +21,7 @@ module XRBP
19
21
  def signal
20
22
  @signalled = true
21
23
  @cv.signal
24
+ self
22
25
  end
23
26
 
24
27
  def wait
@@ -1,10 +1,8 @@
1
1
  module XRBP
2
2
  module WebSocket
3
+ # MultiConnection strategy where connections are tried sequentially
4
+ # until one is found that is open & succeeds
3
5
  class Fallback < MultiConnection
4
- def initialize(*urls)
5
- super(*urls)
6
- end
7
-
8
6
  def next_connection(prev=nil)
9
7
  unless prev.nil?
10
8
  return nil if connections.last == prev
@@ -1,13 +1,31 @@
1
- require_relative '../has_plugin'
2
-
3
1
  module XRBP
4
2
  module WebSocket
3
+ # Base class facilitating transparent multiple
4
+ # connection dispatching. This provides mechanism which
5
+ # to instantiate multiple WebSocket::Connection instances
6
+ # proxying requests to them depending on the *next_connection*
7
+ # selected.
8
+ #
9
+ # This class provides all the common logic to manage
10
+ # multiple connections. Subclasses should override and
11
+ # implement *next_connection* specifying the strategy
12
+ # used to select the connection to use for any given
13
+ # request.
5
14
  class MultiConnection
6
15
  include EventEmitter
7
16
  include HasPlugin
8
17
 
18
+ def plugin_namespace
19
+ WebSocket
20
+ end
21
+
9
22
  attr_reader :connections
10
23
 
24
+ # MultiConnection initializer taking list of urls which
25
+ # to connect to
26
+ #
27
+ # @param urls [Array<String>] list of urls which to establish
28
+ # connections to
11
29
  def initialize(*urls)
12
30
  @connections = []
13
31
 
@@ -20,14 +38,31 @@ module XRBP
20
38
  yield self if block_given?
21
39
  end
22
40
 
41
+ # Force terminate all connections
23
42
  def force_quit!
24
43
  connections.each { |c| c.force_quit! }
25
44
  end
26
45
 
46
+ # Close all connections
27
47
  def close!
28
48
  connections.each { |c| c.close! }
29
49
  end
30
50
 
51
+ # Block until all connections are openend
52
+ def wait_for_open
53
+ connections.each { |c| c.wait_for_open }
54
+ end
55
+
56
+ # Block until all connections are closed
57
+ def wait_for_close
58
+ connections.each { |c| c.wait_for_close }
59
+ end
60
+
61
+ # Block until all connections are completed
62
+ def wait_for_completed
63
+ connections.each { |c| c.wait_for_completed }
64
+ end
65
+
31
66
  alias :_add_plugin :add_plugin
32
67
 
33
68
  def add_plugin(*plg)
@@ -1,5 +1,7 @@
1
1
  module XRBP
2
2
  module WebSocket
3
+ # MultiConnection strategy where requests are sent to
4
+ # all connections in parallel.
3
5
  class Parallel < MultiConnection
4
6
  class All
5
7
  attr_accessor :connections
@@ -15,10 +17,6 @@ module XRBP
15
17
  end
16
18
  end # class All
17
19
 
18
- def initialize(*urls)
19
- super(*urls)
20
- end
21
-
22
20
  def next_connection(prev=nil)
23
21
  return nil unless prev.nil?
24
22
  All.new connections
@@ -1,10 +1,8 @@
1
1
  module XRBP
2
2
  module WebSocket
3
+ # MultiConnection strategy where connections are tried
4
+ # sequentially until one succeeds
3
5
  class Prioritized < MultiConnection
4
- def initialize(*urls)
5
- super(*urls)
6
- end
7
-
8
6
  def next_connection(prev=nil)
9
7
  return nil if prev == connections.last
10
8
  return super if prev.nil?
@@ -1,5 +1,9 @@
1
1
  module XRBP
2
2
  module WebSocket
3
+ # MultiConnection strategy where connections selected in
4
+ # a circular-round robin manner, where the next connection
5
+ # is always used for the next request even if the current
6
+ # one succeeds.
3
7
  class RoundRobin < MultiConnection
4
8
  def initialize(*urls)
5
9
  super(*urls)
@@ -1,12 +1,6 @@
1
1
  module XRBP
2
2
  module WebSocket
3
- def self.plugins
4
- @plugins ||= {}
5
- end
6
-
7
- def self.register_plugin(label, cls)
8
- plugins[label] = cls
9
- end
3
+ include PluginRegistry
10
4
  end # module WebSocket
11
5
  end # module XRPB
12
6
 
@@ -1,22 +1,42 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Plugins
4
- # Automatically reconnects on close events
5
- class AutoConnect
6
- attr_reader :connection
4
+ # Automatically connects on instantiation and reconnects
5
+ # on close events.
6
+ #
7
+ # @example autoconnecting
8
+ # connection = WebSocket::Connection.new "wss://s1.ripple.com:443"
9
+ # connection.add_plugin :autoconnect
10
+ # connection.open? # => true
11
+ class AutoConnect < PluginBase
12
+ attr_accessor :reconnect_delay
7
13
 
8
14
  def initialize(connection)
9
- @connection = connection
15
+ super(connection)
16
+ @reconnect_delay = nil
10
17
  end
11
18
 
12
19
  def added
20
+ plugin = self
21
+
22
+ connection.define_instance_method(:reconnect_delay=) do |d|
23
+ plugin.reconnect_delay = d
24
+
25
+ connections.each{ |c|
26
+ c.plugin(AutoConnect)
27
+ .reconnect_delay = d
28
+ } if self.kind_of?(MultiConnection)
29
+ end
30
+
13
31
  return if connection.kind_of?(MultiConnection)
14
32
 
15
33
  conn = connection
16
-
17
34
  connection.on :completed do
18
35
  connected = false
19
36
  until conn.force_quit? || connected
37
+ conn.rsleep(plugin.reconnect_delay) if plugin.reconnect_delay
38
+ next if conn.force_quit?
39
+
20
40
  begin
21
41
  conn.connect
22
42
  connected = true
@@ -2,6 +2,11 @@ module XRBP
2
2
  module WebSocket
3
3
  module Plugins
4
4
  # Dispatch commands (based on message dispatcher)
5
+ #
6
+ # @example dispatching server info command
7
+ # connection = WebSocket::Connection.new "wss://s1.ripple.com:443"
8
+ # connection.add_plugin :command_dispatcher
9
+ # puts connection.cmd(WebSocket::Cmds::ServerInfo.new)
5
10
  class CommandDispatcher < MessageDispatcher
6
11
  def added
7
12
  super
@@ -1,20 +1,21 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Plugins
4
- # Handles multi-page messages
5
- class CommandPaginator
6
- attr_reader :connection
7
-
8
- def initialize(connection)
9
- @connection = connection
10
- end
11
-
4
+ # Handles multi-page responses, automatically issuing subsequent requests
5
+ # when more data is available and concatinating results.
6
+ #
7
+ # This is most useful with account transaction and object lists where a
8
+ # single account may be associated with more data than can returned in a
9
+ # single result. In this case response will include pagination marker
10
+ # which we leverage here to retrieve all data.
11
+ class CommandPaginator < PluginBase
12
12
  def added
13
13
  raise "Must also include CommandDispatcher plugin" unless connection.plugin?(CommandDispatcher)
14
14
  end
15
15
 
16
16
  def unlock!(cmd, res)
17
17
  return true unless cmd.respond_to?(:paginate?) && cmd.paginate?
18
+ return true unless res["result"] # unlock if we cannot get result
18
19
 
19
20
  marker = res["result"]["marker"]
20
21
  page = res["result"][cmd.page_title]
@@ -2,25 +2,39 @@ module XRBP
2
2
  module WebSocket
3
3
  module Plugins
4
4
  # Automatic disconnection if no server data in certain time
5
- class ConnectionTimeout
6
- attr_reader :connection
5
+ #
6
+ # @example timed out connection
7
+ # connection = WebSocket::Connection.new "wss://s1.ripple.com:443"
8
+ # connection.add_plugin :connection_timeout
9
+ # connection.connection_timeout = 3
10
+ # connection.connect
11
+ # sleep(3)
12
+ # connection.closed? # => true
13
+ class ConnectionTimeout < PluginBase
14
+ include Terminatable
15
+
7
16
  attr_accessor :connection_timeout
8
17
 
9
18
  DEFAULT_TIMEOUT = 10
10
19
 
11
20
  def initialize(connection)
12
- @connection = connection
21
+ super(connection)
13
22
  @connection_timeout = DEFAULT_TIMEOUT
14
23
  end
15
24
 
25
+ def timeout?
26
+ Time.now - @last_msg > @connection_timeout
27
+ end
28
+
16
29
  def added
17
30
  plugin = self
18
31
  connection.define_instance_method(:connection_timeout=) do |t|
19
32
  plugin.connection_timeout = t
20
33
 
21
- self.plugins.find { |plg|
22
- plg.is_a?(ConnectionTimeout)
23
- }.connection_timeout = t if self.kind_of?(MultiConnection)
34
+ connections.each{ |c|
35
+ c.plugin(ConnectionTimeout)
36
+ .connection_timeout = t
37
+ } if self.kind_of?(MultiConnection)
24
38
  end
25
39
  end
26
40
 
@@ -29,22 +43,19 @@ module XRBP
29
43
  end
30
44
 
31
45
  def opened
32
- @thread = Thread.new {
46
+ connection.add_work do
33
47
  @last_msg = Time.now
34
- until connection.force_quit? ||
35
- connection.closed? ||
36
- Thread.current != @thread
37
- if Time.now - @last_msg > @connection_timeout
38
- connection.close!
39
- end
40
-
48
+ until terminate? ||
49
+ connection.force_quit? ||
50
+ connection.closed?
51
+ connection.async_close! if timeout?
41
52
  connection.rsleep(0.1)
42
53
  end
43
- }
54
+ end
44
55
  end
45
56
 
46
57
  def closed
47
- @thread.join unless @thread == Thread.current
58
+ terminate!
48
59
  end
49
60
  end # class ConnectionTimeout
50
61
 
@@ -1,15 +1,30 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Plugins
4
- # Dispatch messages & wait for responses (w/ optional timeout)
5
- class MessageDispatcher
6
- attr_reader :connection, :messages
7
- attr_accessor :message_timeout
4
+ # Dispatch messages & wait for responses (w/ optional timeout).
5
+ # This module allows the client to track messages sent to the server,
6
+ # waiting for responses up to a maximum time. An overridable callback
7
+ # method is provided to match responses to messages. Most often the
8
+ # end-user will not use this plugin directly but rather through
9
+ # CommandDispatcher which inherits it / extends it to issue and
10
+ # track structured commands.
11
+ #
12
+ # @see CommandDispatcher
13
+ class MessageDispatcher < PluginBase
14
+ include Terminatable
15
+ include HasResultParsers
8
16
 
9
17
  DEFAULT_TIMEOUT = 10
10
18
 
19
+ def parsing_plugins
20
+ connection.plugins
21
+ end
22
+
23
+ attr_reader :messages
24
+ attr_accessor :message_timeout
25
+
11
26
  def initialize(connection)
12
- @connection = connection
27
+ super(connection)
13
28
  @message_timeout = DEFAULT_TIMEOUT
14
29
  @messages = []
15
30
  end
@@ -20,9 +35,10 @@ module XRBP
20
35
  connection.define_instance_method(:message_timeout=) do |t|
21
36
  plugin.message_timeout = t
22
37
 
23
- self.plugins.find { |plg|
24
- plg.kind_of?(MessageDipsatcher)
25
- }.message_timeout = t if self.kind_of?(MultiConnection)
38
+ connections.each{ |c|
39
+ c.plugin(MessageDispatcher)
40
+ .message_timeout = t
41
+ } if self.kind_of?(MultiConnection)
26
42
  end
27
43
 
28
44
  connection.define_instance_method(:msg) do |msg, &bl|
@@ -30,11 +46,19 @@ module XRBP
30
46
 
31
47
  msg = Message.new(msg) unless msg.kind_of?(Message)
32
48
  msg.connection = self
49
+ msg.time = Time.now
33
50
  msg.bl = bl if bl
34
51
 
35
52
  unless self.open?
36
- msg.bl.call nil if msg.bl
37
- return nil
53
+ if plugin.try_next(msg)
54
+ return nil if bl
55
+ msg.wait
56
+ return msg.result
57
+
58
+ else
59
+ msg.bl.call nil if bl
60
+ return nil
61
+ end
38
62
  end
39
63
 
40
64
  plugin.messages << msg
@@ -47,9 +71,7 @@ module XRBP
47
71
  end
48
72
 
49
73
  connection.on :close do
50
- plugin.messages.each { |msg|
51
- plugin.cancel_message(msg)
52
- }
74
+ plugin.cancel_all_messages
53
75
  end unless connection.kind_of?(MultiConnection)
54
76
  end
55
77
 
@@ -68,14 +90,6 @@ module XRBP
68
90
  }
69
91
  end
70
92
 
71
- # Allows plugins to convert results
72
- def parse_result(res)
73
- connection.plugins.each { |plg|
74
- res = plg.parse_result(res) if plg != self && plg.respond_to?(:parse_result)
75
- }
76
- res
77
- end
78
-
79
93
  def message(res)
80
94
  req, res = match_message(res)
81
95
  return unless req
@@ -84,10 +98,9 @@ module XRBP
84
98
  return unless unlock!(req, res)
85
99
 
86
100
  begin
87
- res = parse_result(res)
101
+ res = parse_result(res, req)
88
102
  rescue Exception => e
89
- if conn = connection.next_connection(req.connection)
90
- conn.msg(req, &req.bl)
103
+ if try_next(req)
91
104
  return
92
105
 
93
106
  else
@@ -98,6 +111,14 @@ module XRBP
98
111
  req.bl.call(res)
99
112
  end
100
113
 
114
+ def try_next(msg)
115
+ conn = connection.next_connection(msg.connection)
116
+ return false unless !!conn
117
+ messages.delete(msg)
118
+ conn.msg(msg, &msg.bl)
119
+ true
120
+ end
121
+
101
122
  def cancel_message(msg)
102
123
  connection.state_mutex.synchronize {
103
124
  messages.delete(msg)
@@ -105,33 +126,42 @@ module XRBP
105
126
  }
106
127
  end
107
128
 
129
+ def cancel_all_messages
130
+ messages.each { |msg|
131
+ cancel_message(msg)
132
+ }
133
+ end
134
+
135
+ public
136
+
108
137
  def opened
109
- @timeout_thread = Thread.new {
138
+ connection.add_work do
110
139
  # XXX remove force_quit? condition check from this loop,
111
140
  # so we're sure messages always timeout, even on force quit.
112
141
  # Always ensure close! is called after websocket is no longer
113
142
  # being used!
114
- until connection.closed? || Thread.current != @timeout_thread
143
+ until terminate? || connection.closed?
115
144
  now = Time.now
116
145
  tmsgs = Array.new(messages)
117
146
  tmsgs.each { |msg|
118
147
  if now - msg.time > @message_timeout
119
148
  connection.emit :timeout, msg
120
- cancel_message(msg)
149
+
150
+ cancel_message(msg) unless try_next(msg)
121
151
 
122
152
  # XXX manually close the connection as
123
153
  # a broken pipe will not stop websocket polling
124
- connection.close!
154
+ connection.async_close!
125
155
  end
126
156
  }
127
157
 
128
158
  connection.rsleep(0.1)
129
159
  end
130
- }
160
+ end
131
161
  end
132
162
 
133
163
  def closed
134
- @timeout_thread.join unless @timeout_thread == Thread.current
164
+ terminate!
135
165
  end
136
166
  end # class MessageDispatcher
137
167