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