lightstreamer 0.8 → 0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -1
- data/README.md +19 -14
- data/lib/lightstreamer.rb +2 -0
- data/lib/lightstreamer/cli/commands/stream_command.rb +21 -7
- data/lib/lightstreamer/session.rb +41 -36
- data/lib/lightstreamer/stream_connection.rb +1 -1
- data/lib/lightstreamer/subscription.rb +12 -13
- data/lib/lightstreamer/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75687f5d599512c6623967cd0e005f67c616e3f2
|
4
|
+
data.tar.gz: b8eadafa92da0812fa59803e44b371d4537aeedb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8a46f18e3bd81a5635c660caea2911e1f04f5503476293e37bebd49bf0a45a3f28a980bc546b8d316a419aad5c0b550d5134e1dab62ff7a513cf81fe20299e7
|
7
|
+
data.tar.gz: 273ec4437edcde9a079a0f7c51e83701751caae948a776c623d89443ed7f9f25eb556c9255fda11d44e1c4b76785fae93b19d19b56d731938dd0e3eb34ad129e
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# Lightstreamer Changelog
|
2
2
|
|
3
|
+
### 0.9 — August 4, 2016
|
4
|
+
|
5
|
+
- Replaced the `Lightstreamer::Session#error` attribute with a new `Lightstreamer::Session#on_error` callback to enable
|
6
|
+
more efficient error handling and notification
|
7
|
+
- Fixed issues with `Lightstreamer::Session#bulk_subscription_start`
|
8
|
+
|
3
9
|
### 0.8 — August 2, 2016
|
4
10
|
|
5
11
|
- Added support for Lightstreamer's `COMMAND` and `RAW` subscription modes
|
6
12
|
- Added support for Lightstreamer's polling mode and switching an active session between streaming and polling
|
7
|
-
- `Lightstreamer::Subscription#item_data` now
|
13
|
+
- `Lightstreamer::Subscription#item_data` now returns `nil` if the requested item has never had any data set
|
8
14
|
- Renamed `Lightstreamer::Subscription#adapter` to `Lightstreamer::Subscription#data_adapter` and the `--adapter`
|
9
15
|
command-line option to `--data-adapter`
|
10
16
|
- The `--mode` command-line option is now required because the default value of `merge` has been removed
|
data/README.md
CHANGED
@@ -15,11 +15,11 @@ provided command-line client. Written against the
|
|
15
15
|
Includes support for:
|
16
16
|
|
17
17
|
- Streaming and polling connections
|
18
|
-
-
|
19
|
-
- Automatic management of table content when in
|
18
|
+
- All subscription modes: command, distinct, merge and raw
|
19
|
+
- Automatic management of table content when in command mode
|
20
20
|
- Silent subscriptions
|
21
|
-
- Item snapshots
|
22
|
-
- Unfiltered subscriptions and
|
21
|
+
- Item snapshots and end-of-snapshot notifications
|
22
|
+
- Unfiltered subscriptions and overflow notifications
|
23
23
|
- Bulk subscription creation
|
24
24
|
- Synchronous and asynchronous message sending
|
25
25
|
- Detailed error reporting and error handling callbacks
|
@@ -43,23 +43,28 @@ The two primary classes that make up the public API are:
|
|
43
43
|
- [`Lightstreamer::Session`](http://www.rubydoc.info/github/rviney/lightstreamer/Lightstreamer/Session)
|
44
44
|
- [`Lightstreamer::Subscription`](http://www.rubydoc.info/github/rviney/lightstreamer/Lightstreamer/Subscription)
|
45
45
|
|
46
|
-
The following code
|
47
|
-
streaming output as it arrives.
|
46
|
+
The following code demonstrates how to create a Lightstreamer session, build a subscription, then use a thread-safe
|
47
|
+
queue to print streaming output as it arrives.
|
48
48
|
|
49
49
|
```ruby
|
50
50
|
require 'lightstreamer'
|
51
51
|
|
52
|
-
# Create a new session that connects to the Lightstreamer demo server
|
52
|
+
# Create a new session that connects to the Lightstreamer demo server
|
53
53
|
session = Lightstreamer::Session.new server_url: 'http://push.lightstreamer.com',
|
54
54
|
adapter_set: 'DEMO', username: '', password: ''
|
55
55
|
|
56
|
+
# Add a simple error handler that just raises the error and so terminates the application
|
57
|
+
session.on_error do |error|
|
58
|
+
raise error
|
59
|
+
end
|
60
|
+
|
56
61
|
# Connect the session
|
57
62
|
session.connect
|
58
63
|
|
59
64
|
# Create a new subscription that subscribes to thirty items and to four fields on each item
|
60
|
-
subscription = session.build_subscription
|
65
|
+
subscription = session.build_subscription data_adapter: 'QUOTE_ADAPTER', mode: :merge,
|
66
|
+
items: (1..30).map { |i| "item#{i}" },
|
61
67
|
fields: [:ask, :bid, :stock_name, :time],
|
62
|
-
mode: :merge, data_adapter: 'QUOTE_ADAPTER'
|
63
68
|
|
64
69
|
# Create a thread-safe queue
|
65
70
|
queue = Queue.new
|
@@ -73,7 +78,7 @@ end
|
|
73
78
|
# Start streaming data for the subscription and request an initial snapshot
|
74
79
|
subscription.start snapshot: true
|
75
80
|
|
76
|
-
#
|
81
|
+
# Print new data as soon as it becomes available on the queue
|
77
82
|
loop do
|
78
83
|
data = queue.pop
|
79
84
|
puts "#{data[:time]} - #{data[:stock_name]} - bid: #{data[:bid]}, ask: #{data[:ask]}"
|
@@ -82,10 +87,10 @@ end
|
|
82
87
|
|
83
88
|
## Usage — Command-Line Client
|
84
89
|
|
85
|
-
This gem provides a simple command-line client that can connect to a Lightstreamer server
|
86
|
-
|
90
|
+
This gem provides a simple command-line client that can connect to a Lightstreamer server and display
|
91
|
+
live streaming output for a set of items and fields.
|
87
92
|
|
88
|
-
To
|
93
|
+
To stream data from Lightstreamer's demo server run the following command:
|
89
94
|
|
90
95
|
```
|
91
96
|
lightstreamer --server-url http://push.lightstreamer.com --adapter-set DEMO \
|
@@ -101,7 +106,7 @@ lightstreamer help stream
|
|
101
106
|
|
102
107
|
## Documentation
|
103
108
|
|
104
|
-
API documentation is available [here](http://www.rubydoc.info/github/rviney/lightstreamer).
|
109
|
+
API documentation is available [here](http://www.rubydoc.info/github/rviney/lightstreamer/master).
|
105
110
|
|
106
111
|
## Contributors
|
107
112
|
|
data/lib/lightstreamer.rb
CHANGED
@@ -20,26 +20,36 @@ module Lightstreamer
|
|
20
20
|
option :maximum_update_frequency, desc: 'The maximum number of updates per second for each item'
|
21
21
|
|
22
22
|
def stream
|
23
|
-
|
23
|
+
prepare_stream
|
24
24
|
|
25
|
-
|
26
|
-
create_subscription
|
25
|
+
puts "Session ID: #{@session.session_id}"
|
27
26
|
|
28
27
|
loop do
|
29
|
-
|
28
|
+
data = @queue.pop
|
30
29
|
|
31
|
-
|
30
|
+
if data.is_a? Lightstreamer::LightstreamerError
|
31
|
+
puts "Error: #{data}"
|
32
|
+
break
|
33
|
+
end
|
34
|
+
|
35
|
+
puts data
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
35
39
|
private
|
36
40
|
|
41
|
+
def prepare_stream
|
42
|
+
@queue = Queue.new
|
43
|
+
|
44
|
+
create_session
|
45
|
+
create_subscription
|
46
|
+
end
|
47
|
+
|
37
48
|
def create_session
|
38
49
|
@session = Lightstreamer::Session.new session_options
|
39
50
|
@session.connect
|
40
51
|
@session.on_message_result(&method(:on_message_result))
|
41
|
-
|
42
|
-
puts "Session ID: #{@session.session_id}"
|
52
|
+
@session.on_error(&method(:on_error))
|
43
53
|
end
|
44
54
|
|
45
55
|
def create_subscription
|
@@ -79,6 +89,10 @@ module Lightstreamer
|
|
79
89
|
def on_end_of_snapshot(_subscription, item_name)
|
80
90
|
@queue.push "End of snapshot for item #{item_name}"
|
81
91
|
end
|
92
|
+
|
93
|
+
def on_error(error)
|
94
|
+
@queue.push error
|
95
|
+
end
|
82
96
|
end
|
83
97
|
end
|
84
98
|
end
|
@@ -25,13 +25,6 @@ module Lightstreamer
|
|
25
25
|
# @return [String, nil]
|
26
26
|
attr_reader :adapter_set
|
27
27
|
|
28
|
-
# If an error occurs on the stream connection that causes the session to terminate then details of the error will be
|
29
|
-
# stored in this attribute. If the session is terminated as a result of calling {#disconnect} then the error will be
|
30
|
-
# {Errors::SessionEndError}.
|
31
|
-
#
|
32
|
-
# @return [LightstreamerError, nil]
|
33
|
-
attr_reader :error
|
34
|
-
|
35
28
|
# The server-side bandwidth constraint on data usage, expressed in kbps. If this is zero then no limit is applied.
|
36
29
|
#
|
37
30
|
# @return [Float]
|
@@ -58,8 +51,7 @@ module Lightstreamer
|
|
58
51
|
# @option options [Boolean] :polling_enabled Whether polling mode is enabled. See {#polling_enabled} for details.
|
59
52
|
# Defaults to `false`.
|
60
53
|
def initialize(options = {})
|
61
|
-
@
|
62
|
-
@subscriptions_mutex = Mutex.new
|
54
|
+
@mutex = Mutex.new
|
63
55
|
|
64
56
|
@server_url = options.fetch :server_url
|
65
57
|
@username = options[:username]
|
@@ -68,7 +60,8 @@ module Lightstreamer
|
|
68
60
|
@requested_maximum_bandwidth = options[:requested_maximum_bandwidth].to_f
|
69
61
|
@polling_enabled = options[:polling_enabled]
|
70
62
|
|
71
|
-
@
|
63
|
+
@subscriptions = []
|
64
|
+
@callbacks = { on_message_result: [], on_error: [] }
|
72
65
|
end
|
73
66
|
|
74
67
|
# Connects a new Lightstreamer session using the details passed to {#initialize}. If an error occurs then
|
@@ -76,8 +69,6 @@ module Lightstreamer
|
|
76
69
|
def connect
|
77
70
|
return if @stream_connection
|
78
71
|
|
79
|
-
@error = nil
|
80
|
-
|
81
72
|
@stream_connection = StreamConnection.new self
|
82
73
|
@stream_connection.connect
|
83
74
|
|
@@ -110,9 +101,7 @@ module Lightstreamer
|
|
110
101
|
@stream_connection.disconnect if @stream_connection
|
111
102
|
@processing_thread.exit if @processing_thread
|
112
103
|
|
113
|
-
@subscriptions.each
|
114
|
-
subscription.instance_variable_set :@active, false
|
115
|
-
end
|
104
|
+
@subscriptions.each { |subscription| subscription.instance_variable_set :@active, false }
|
116
105
|
|
117
106
|
@processing_thread = @stream_connection = nil
|
118
107
|
end
|
@@ -124,9 +113,7 @@ module Lightstreamer
|
|
124
113
|
# it forces the stream connection to rebind using the new setting. If an error occurs then a {LightstreamerError}
|
125
114
|
# subclass will be raised.
|
126
115
|
def force_rebind
|
127
|
-
|
128
|
-
|
129
|
-
control_request :force_rebind
|
116
|
+
control_request :force_rebind if @stream_connection
|
130
117
|
end
|
131
118
|
|
132
119
|
# Builds a new subscription for this session with the specified options. Note that ths does not activate the
|
@@ -149,7 +136,7 @@ module Lightstreamer
|
|
149
136
|
def build_subscription(options)
|
150
137
|
subscription = Subscription.new self, options
|
151
138
|
|
152
|
-
@
|
139
|
+
@mutex.synchronize { @subscriptions << subscription }
|
153
140
|
|
154
141
|
subscription
|
155
142
|
end
|
@@ -162,7 +149,7 @@ module Lightstreamer
|
|
162
149
|
def remove_subscription(subscription)
|
163
150
|
subscription.stop
|
164
151
|
|
165
|
-
@
|
152
|
+
@mutex.synchronize { @subscriptions.delete subscription }
|
166
153
|
end
|
167
154
|
|
168
155
|
# This method performs a bulk {Subscription#start} on all the passed subscriptions. Calling {Subscription#start} on
|
@@ -177,10 +164,11 @@ module Lightstreamer
|
|
177
164
|
# @return [Array<LightstreamerError, nil>]
|
178
165
|
def bulk_subscription_start(*subscriptions)
|
179
166
|
request_bodies = subscriptions.map do |subscription|
|
180
|
-
|
167
|
+
args = subscription.start_control_request_args
|
168
|
+
PostRequest.request_body({ LS_session: session_id, LS_op: args.first }.merge(args[1]))
|
181
169
|
end
|
182
170
|
|
183
|
-
errors = PostRequest.bulk_execute
|
171
|
+
errors = PostRequest.bulk_execute control_request_url, request_bodies
|
184
172
|
|
185
173
|
# Set @active to true on all subscriptions that did not have an error
|
186
174
|
errors.each_with_index do |error, index|
|
@@ -232,7 +220,7 @@ module Lightstreamer
|
|
232
220
|
#
|
233
221
|
# @param [Proc] callback The callback that is to be run.
|
234
222
|
def on_message_result(&callback)
|
235
|
-
@
|
223
|
+
@mutex.synchronize { @callbacks[:on_message_result] << callback }
|
236
224
|
end
|
237
225
|
|
238
226
|
# Sends a request to this session's control connection. If an error occurs then a {LightstreamerError} subclass will
|
@@ -241,34 +229,49 @@ module Lightstreamer
|
|
241
229
|
# @param [Symbol] operation The control operation to perform.
|
242
230
|
# @param [Hash] options The options to send with the control request.
|
243
231
|
def control_request(operation, options = {})
|
244
|
-
|
232
|
+
PostRequest.execute control_request_url, options.merge(LS_session: session_id, LS_op: operation)
|
233
|
+
end
|
245
234
|
|
246
|
-
|
235
|
+
# Adds the passed block to the list of callbacks that will be run when this session encounters an error on its
|
236
|
+
# processing thread caused by an error with the steam connection. The block will be called on a worker thread and so
|
237
|
+
# the code that is run by the block must be thread-safe. The argument passed to the block is `|error|`, which will
|
238
|
+
# be a {LightstreamerError} subclass detailing the error that occurred.
|
239
|
+
#
|
240
|
+
# @param [Proc] callback The callback that is to be run.
|
241
|
+
def on_error(&callback)
|
242
|
+
@mutex.synchronize { @callbacks[:on_error] << callback }
|
247
243
|
end
|
248
244
|
|
249
245
|
private
|
250
246
|
|
247
|
+
def control_request_url
|
248
|
+
URI.join(@stream_connection.control_address, '/lightstreamer/control.txt').to_s
|
249
|
+
end
|
250
|
+
|
251
251
|
# Starts the processing thread that reads and processes incoming data from the stream connection.
|
252
252
|
def create_processing_thread
|
253
253
|
@processing_thread = Thread.new do
|
254
254
|
Thread.current.abort_on_exception = true
|
255
255
|
|
256
|
-
loop
|
257
|
-
line = @stream_connection.read_line
|
258
|
-
break if line.nil?
|
259
|
-
|
260
|
-
process_stream_line line
|
261
|
-
end
|
256
|
+
loop { break unless processing_thread_tick @stream_connection.read_line }
|
262
257
|
|
263
|
-
# The stream connection has terminated so the session is assumed to be over
|
264
|
-
@error = @stream_connection.error
|
265
258
|
@processing_thread = @stream_connection = nil
|
266
259
|
end
|
267
260
|
end
|
268
261
|
|
262
|
+
def processing_thread_tick(line)
|
263
|
+
if line
|
264
|
+
process_stream_line line
|
265
|
+
true
|
266
|
+
else
|
267
|
+
@mutex.synchronize { @callbacks[:on_error].each { |callback| callback.call @stream_connection.error } }
|
268
|
+
false
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
269
272
|
# Processes a single line of incoming stream data. This method is always run on the processing thread.
|
270
273
|
def process_stream_line(line)
|
271
|
-
return if @subscriptions.any? { |subscription| subscription.process_stream_data line }
|
274
|
+
return if @mutex.synchronize { @subscriptions.any? { |subscription| subscription.process_stream_data line } }
|
272
275
|
return if process_send_message_outcome line
|
273
276
|
|
274
277
|
warn "Lightstreamer: unprocessed stream data '#{line}'"
|
@@ -279,8 +282,10 @@ module Lightstreamer
|
|
279
282
|
outcome = SendMessageOutcomeMessage.parse line
|
280
283
|
return unless outcome
|
281
284
|
|
282
|
-
@
|
283
|
-
|
285
|
+
@mutex.synchronize do
|
286
|
+
@callbacks[:on_message_result].each do |callback|
|
287
|
+
callback.call outcome.sequence, outcome.numbers, outcome.error
|
288
|
+
end
|
284
289
|
end
|
285
290
|
|
286
291
|
true
|
@@ -172,7 +172,7 @@ module Lightstreamer
|
|
172
172
|
def process_body_line(line)
|
173
173
|
if line =~ /^LOOP( \d+|)$/
|
174
174
|
@loop = true
|
175
|
-
elsif line =~ /^END( \d+|)
|
175
|
+
elsif line =~ /^END( \d+|)$/
|
176
176
|
@error = Errors::SessionEndError.new line[4..-1]
|
177
177
|
elsif !line.empty?
|
178
178
|
@queue.push line
|
@@ -56,8 +56,9 @@ module Lightstreamer
|
|
56
56
|
#
|
57
57
|
# @private
|
58
58
|
def initialize(session, options)
|
59
|
-
@
|
59
|
+
@mutex = Mutex.new
|
60
60
|
|
61
|
+
@session = session
|
61
62
|
@items = options.fetch(:items)
|
62
63
|
@fields = options.fetch(:fields)
|
63
64
|
@mode = options.fetch(:mode).to_sym
|
@@ -65,8 +66,6 @@ module Lightstreamer
|
|
65
66
|
@selector = options[:selector]
|
66
67
|
@maximum_update_frequency = sanitize_frequency options[:maximum_update_frequency]
|
67
68
|
|
68
|
-
@data_mutex = Mutex.new
|
69
|
-
|
70
69
|
clear_data
|
71
70
|
clear_callbacks
|
72
71
|
end
|
@@ -148,7 +147,7 @@ module Lightstreamer
|
|
148
147
|
# Clears all current data stored for this subscription. New data will continue to be processed as it becomes
|
149
148
|
# available.
|
150
149
|
def clear_data
|
151
|
-
@
|
150
|
+
@mutex.synchronize { @data = (0...items.size).map { SubscriptionItemData.new } }
|
152
151
|
end
|
153
152
|
|
154
153
|
# Returns a copy of the current data of one of this subscription's items. If {#mode} is `:merge` then the returned
|
@@ -163,7 +162,7 @@ module Lightstreamer
|
|
163
162
|
index = @items.index item_name
|
164
163
|
raise ArgumentError, 'Unknown item' unless index
|
165
164
|
|
166
|
-
@
|
165
|
+
@mutex.synchronize { @data[index].data && @data[index].data.dup }
|
167
166
|
end
|
168
167
|
|
169
168
|
# Sets the current data for the item with the specified name. This is only allowed when {mode} is `:command` or
|
@@ -177,7 +176,7 @@ module Lightstreamer
|
|
177
176
|
index = @items.index item_name
|
178
177
|
raise ArgumentError, 'Unknown item' unless index
|
179
178
|
|
180
|
-
@
|
179
|
+
@mutex.synchronize { @data[index].set_data item_data, mode }
|
181
180
|
end
|
182
181
|
|
183
182
|
# Adds the passed block to the list of callbacks that will be run when new data for this subscription arrives. The
|
@@ -187,7 +186,7 @@ module Lightstreamer
|
|
187
186
|
#
|
188
187
|
# @param [Proc] callback The callback that is to be run when new data arrives.
|
189
188
|
def on_data(&callback)
|
190
|
-
@
|
189
|
+
@mutex.synchronize { @callbacks[:on_data] << callback }
|
191
190
|
end
|
192
191
|
|
193
192
|
# Adds the passed block to the list of callbacks that will be run when the server reports an overflow for this
|
@@ -198,7 +197,7 @@ module Lightstreamer
|
|
198
197
|
#
|
199
198
|
# @param [Proc] callback The callback that is to be run when an overflow is reported for this subscription.
|
200
199
|
def on_overflow(&callback)
|
201
|
-
@
|
200
|
+
@mutex.synchronize { @callbacks[:on_overflow] << callback }
|
202
201
|
end
|
203
202
|
|
204
203
|
# Adds the passed block to the list of callbacks that will be run when the server reports an end-of-snapshot
|
@@ -209,12 +208,12 @@ module Lightstreamer
|
|
209
208
|
#
|
210
209
|
# @param [Proc] callback The callback that is to be run when an overflow is reported for this subscription.
|
211
210
|
def on_end_of_snapshot(&callback)
|
212
|
-
@
|
211
|
+
@mutex.synchronize { @callbacks[:on_end_of_snapshot] << callback }
|
213
212
|
end
|
214
213
|
|
215
214
|
# Removes all {#on_data}, {#on_overflow} and {#on_end_of_snapshot} callbacks present on this subscription.
|
216
215
|
def clear_callbacks
|
217
|
-
@
|
216
|
+
@mutex.synchronize { @callbacks = { on_data: [], on_overflow: [], on_end_of_snapshot: [] } }
|
218
217
|
end
|
219
218
|
|
220
219
|
# Processes a line of stream data if it is relevant to this subscription. This method is thread-safe and is intended
|
@@ -242,7 +241,7 @@ module Lightstreamer
|
|
242
241
|
def process_update_message(message)
|
243
242
|
return unless message
|
244
243
|
|
245
|
-
@
|
244
|
+
@mutex.synchronize do
|
246
245
|
@data[message.item_index].send "process_new_#{mode}_data", message.data.dup
|
247
246
|
run_callbacks :on_data, @items[message.item_index], @data[message.item_index].data, message.data
|
248
247
|
end
|
@@ -251,13 +250,13 @@ module Lightstreamer
|
|
251
250
|
def process_overflow_message(message)
|
252
251
|
return unless message
|
253
252
|
|
254
|
-
@
|
253
|
+
@mutex.synchronize { run_callbacks :on_overflow, @items[message.item_index], message.overflow_size }
|
255
254
|
end
|
256
255
|
|
257
256
|
def process_end_of_snapshot_message(message)
|
258
257
|
return unless message
|
259
258
|
|
260
|
-
@
|
259
|
+
@mutex.synchronize { run_callbacks :on_end_of_snapshot, @items[message.item_index] }
|
261
260
|
end
|
262
261
|
|
263
262
|
def run_callbacks(callback_type, *args)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lightstreamer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.9'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Viney
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: excon
|