xrbp 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +103 -5
- data/examples/accounts.rb +13 -0
- data/examples/autoconnect_timeout.rb +5 -1
- data/examples/autorety.rb +7 -0
- data/examples/crawl_nodes.rb +32 -0
- data/examples/dsl/account.rb +16 -0
- data/examples/dsl/ledger.rb +7 -0
- data/examples/dsl/ledger_subscribe.rb +18 -0
- data/examples/dsl/validators.rb +8 -0
- data/examples/gateways.rb +8 -0
- data/examples/latest_account.rb +4 -0
- data/examples/ledger_multi_subscribe.rb +1 -1
- data/examples/ledger_subscribe.rb +2 -2
- data/examples/market.rb +13 -0
- data/examples/username.rb +12 -0
- data/examples/validator.rb +8 -0
- data/lib/xrbp.rb +5 -1
- data/lib/xrbp/common.rb +10 -0
- data/lib/xrbp/core_ext.rb +24 -0
- data/lib/xrbp/dsl.rb +25 -0
- data/lib/xrbp/dsl/accounts.rb +13 -0
- data/lib/xrbp/dsl/ledgers.rb +23 -0
- data/lib/xrbp/dsl/validators.rb +10 -0
- data/lib/xrbp/dsl/webclient.rb +8 -0
- data/lib/xrbp/dsl/websocket.rb +28 -0
- data/lib/xrbp/model.rb +4 -0
- data/lib/xrbp/model/account.rb +142 -1
- data/lib/xrbp/model/base.rb +1 -0
- data/lib/xrbp/model/gateway.rb +24 -0
- data/lib/xrbp/model/ledger.rb +30 -1
- data/lib/xrbp/model/market.rb +52 -0
- data/lib/xrbp/model/node.rb +131 -0
- data/lib/xrbp/model/parsers/account.rb +44 -0
- data/lib/xrbp/model/parsers/gateway.rb +40 -0
- data/lib/xrbp/model/parsers/market.rb +28 -0
- data/lib/xrbp/model/parsers/node.rb +19 -0
- data/lib/xrbp/model/parsers/quote.rb +47 -0
- data/lib/xrbp/model/parsers/validator.rb +25 -0
- data/lib/xrbp/model/validator.rb +24 -0
- data/lib/xrbp/plugins.rb +6 -0
- data/lib/xrbp/plugins/base.rb +10 -0
- data/lib/xrbp/plugins/has_plugin.rb +45 -0
- data/lib/xrbp/plugins/has_result_parsers.rb +27 -0
- data/lib/xrbp/plugins/plugin_registry.rb +20 -0
- data/lib/xrbp/plugins/result_parser.rb +19 -0
- data/lib/xrbp/terminatable.rb +19 -0
- data/lib/xrbp/thread_registry.rb +22 -0
- data/lib/xrbp/version.rb +1 -1
- data/lib/xrbp/webclient.rb +2 -0
- data/lib/xrbp/webclient/connection.rb +100 -0
- data/lib/xrbp/webclient/plugins.rb +8 -0
- data/lib/xrbp/webclient/plugins/autoretry.rb +54 -0
- data/lib/xrbp/webclient/plugins/result_parser.rb +23 -0
- data/lib/xrbp/websocket/client.rb +85 -24
- data/lib/xrbp/websocket/cmds/account_info.rb +4 -0
- data/lib/xrbp/websocket/cmds/account_lines.rb +5 -0
- data/lib/xrbp/websocket/cmds/account_objects.rb +4 -0
- data/lib/xrbp/websocket/cmds/account_offers.rb +5 -0
- data/lib/xrbp/websocket/cmds/account_tx.rb +4 -0
- data/lib/xrbp/websocket/cmds/book_offers.rb +4 -0
- data/lib/xrbp/websocket/cmds/ledger.rb +3 -0
- data/lib/xrbp/websocket/cmds/ledger_entry.rb +4 -0
- data/lib/xrbp/websocket/cmds/paginated.rb +4 -1
- data/lib/xrbp/websocket/cmds/server_info.rb +4 -0
- data/lib/xrbp/websocket/cmds/subscribe.rb +4 -0
- data/lib/xrbp/websocket/command.rb +11 -0
- data/lib/xrbp/websocket/connection.rb +75 -22
- data/lib/xrbp/websocket/message.rb +5 -2
- data/lib/xrbp/websocket/multi/fallback.rb +2 -4
- data/lib/xrbp/websocket/multi/multi_connection.rb +37 -2
- data/lib/xrbp/websocket/multi/parallel.rb +2 -4
- data/lib/xrbp/websocket/multi/prioritized.rb +2 -4
- data/lib/xrbp/websocket/multi/round_robin.rb +4 -0
- data/lib/xrbp/websocket/plugins.rb +1 -7
- data/lib/xrbp/websocket/plugins/autoconnect.rb +25 -5
- data/lib/xrbp/websocket/plugins/command_dispatcher.rb +5 -0
- data/lib/xrbp/websocket/plugins/command_paginator.rb +9 -8
- data/lib/xrbp/websocket/plugins/connection_timeout.rb +27 -16
- data/lib/xrbp/websocket/plugins/message_dispatcher.rb +60 -30
- data/lib/xrbp/websocket/plugins/result_parser.rb +19 -19
- data/lib/xrbp/websocket/socket.rb +23 -6
- metadata +118 -8
- data/lib/xrbp/network.rb +0 -6
- data/lib/xrbp/network/nodes.rb +0 -8
- 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
|
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,22 +1,42 @@
|
|
1
1
|
module XRBP
|
2
2
|
module WebSocket
|
3
3
|
module Plugins
|
4
|
-
# Automatically
|
5
|
-
|
6
|
-
|
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
|
-
|
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
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
6
|
-
|
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
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
46
|
+
connection.add_work do
|
33
47
|
@last_msg = Time.now
|
34
|
-
until
|
35
|
-
connection.
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
37
|
-
|
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.
|
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
|
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
|
-
|
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
|
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
|
-
|
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.
|
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
|
-
|
164
|
+
terminate!
|
135
165
|
end
|
136
166
|
end # class MessageDispatcher
|
137
167
|
|