lightstreamer 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +5 -5
- data/lib/lightstreamer.rb +4 -1
- data/lib/lightstreamer/control_connection.rb +6 -8
- data/lib/lightstreamer/session.rb +28 -37
- data/lib/lightstreamer/stream_connection.rb +100 -35
- data/lib/lightstreamer/stream_connection_header.rb +60 -0
- data/lib/lightstreamer/subscription.rb +3 -16
- data/lib/lightstreamer/utf16.rb +31 -0
- data/lib/lightstreamer/version.rb +1 -1
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f96a70c42b710644985ba440779ec2d73f32ad9e
|
4
|
+
data.tar.gz: 4c0181a0612a67870d3333671ecbb20e0af27f8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5048265eaf80652222323ffad4f4fa4a39343c4adbc8a52ae5b449d43a8c63e343334cab654a2f6fdc3321955b418581661e0c596e23f1f3ea83f953e9a77f02
|
7
|
+
data.tar.gz: 248b20766df771c73189fd3eb61f218a5b016254a2271f965c4076cfde2bd9391eef4ab6afcc1ea5a5424a3ca86c4f15728aeb12bf415bb2b03721722828bced
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Lightstreamer Changelog
|
2
2
|
|
3
|
+
### 0.3 — July 24, 2016
|
4
|
+
|
5
|
+
- Seamlessly rebind the stream connection when a `LOOP` message is received
|
6
|
+
- Correctly handle UTF-16 escape sequences in stream data, including UTF-16 surrogate pairs
|
7
|
+
- Switched to the `typhoeus` library for HTTP support
|
8
|
+
- Improved error handling on the stream thread
|
9
|
+
- Added `Lightstreamer::Session#connected?`
|
10
|
+
|
3
11
|
### 0.2 — July 23, 2016
|
4
12
|
|
5
13
|
- Added complete test suite
|
data/README.md
CHANGED
@@ -43,9 +43,9 @@ session = Lightstreamer::Session.new server_url: 'http://push.lightstreamer.com'
|
|
43
43
|
# Connect the session
|
44
44
|
session.connect
|
45
45
|
|
46
|
-
# Create a new subscription that subscribes to
|
47
|
-
subscription = Lightstreamer::Subscription.new items:
|
48
|
-
fields: [:
|
46
|
+
# Create a new subscription that subscribes to thirty items and to four fields on each item
|
47
|
+
subscription = Lightstreamer::Subscription.new items: (1..30).map { |i| "item#{i}" },
|
48
|
+
fields: [:ask, :bid, :stock_name, :time],
|
49
49
|
mode: :merge, adapter: 'QUOTE_ADAPTER'
|
50
50
|
|
51
51
|
# Create a thread-safe queue
|
@@ -76,10 +76,10 @@ To print streaming data from the demo server run the following command:
|
|
76
76
|
|
77
77
|
```
|
78
78
|
lightstreamer --server-url http://push.lightstreamer.com --adapter-set DEMO --adapter QUOTE_ADAPTER \
|
79
|
-
--items item1 item2 item3 item4 item5 --fields
|
79
|
+
--items item1 item2 item3 item4 item5 --fields ask bid stock_name time
|
80
80
|
```
|
81
81
|
|
82
|
-
To see a full list of available options run the following command:
|
82
|
+
To see a full list of available options for the command-line client run the following command:
|
83
83
|
|
84
84
|
```
|
85
85
|
lightstreamer help stream
|
data/lib/lightstreamer.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require 'rest-client'
|
2
1
|
require 'thor'
|
2
|
+
require 'typhoeus'
|
3
|
+
require 'uri'
|
3
4
|
|
4
5
|
require 'lightstreamer/control_connection'
|
5
6
|
require 'lightstreamer/line_buffer'
|
@@ -7,7 +8,9 @@ require 'lightstreamer/protocol_error'
|
|
7
8
|
require 'lightstreamer/request_error'
|
8
9
|
require 'lightstreamer/session'
|
9
10
|
require 'lightstreamer/stream_connection'
|
11
|
+
require 'lightstreamer/stream_connection_header'
|
10
12
|
require 'lightstreamer/subscription'
|
13
|
+
require 'lightstreamer/utf16'
|
11
14
|
require 'lightstreamer/version'
|
12
15
|
|
13
16
|
require 'lightstreamer/cli/main'
|
@@ -31,16 +31,14 @@ module Lightstreamer
|
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
|
-
# Executes a POST request to the control address with the specified payload. Raises
|
35
|
-
# fails. Returns the response
|
34
|
+
# Executes a POST request to the control address with the specified payload. Raises {RequestError} if the HTTP
|
35
|
+
# request fails. Returns the response body split into individual lines.
|
36
36
|
def execute_post_request(payload)
|
37
|
-
response =
|
37
|
+
response = Typhoeus.post @control_url, body: payload
|
38
|
+
|
39
|
+
raise RequestError.new(response.return_message, response.response_code) unless response.success?
|
38
40
|
|
39
41
|
response.body.split("\n").map(&:strip)
|
40
|
-
rescue RestClient::Exception => exception
|
41
|
-
raise RequestError, exception.message
|
42
|
-
rescue SocketError => socket_error
|
43
|
-
raise RequestError, socket_error
|
44
42
|
end
|
45
43
|
|
46
44
|
# Constructs the payload for a Lightstreamer control request based on the given options hash. See {#execute} for
|
@@ -54,7 +52,7 @@ module Lightstreamer
|
|
54
52
|
|
55
53
|
build_optional_payload_fields options, params
|
56
54
|
|
57
|
-
|
55
|
+
params
|
58
56
|
end
|
59
57
|
|
60
58
|
def build_optional_payload_fields(options, params)
|
@@ -36,20 +36,22 @@ module Lightstreamer
|
|
36
36
|
def connect
|
37
37
|
return if @stream_connection
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
create_stream_connection
|
40
|
+
create_control_connection
|
41
|
+
create_processing_thread
|
42
|
+
rescue
|
43
|
+
@stream_connection = nil
|
44
|
+
raise
|
45
|
+
end
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
handle_connection_error
|
49
|
-
end
|
47
|
+
# Returns whether this session is currently connected and has an active stream connection.
|
48
|
+
#
|
49
|
+
# @return [Boolean]
|
50
|
+
def connected?
|
51
|
+
!@stream_connection.nil?
|
50
52
|
end
|
51
53
|
|
52
|
-
# Disconnects this session and shuts down its stream and processing threads.
|
54
|
+
# Disconnects this session and shuts down its stream connection and processing threads.
|
53
55
|
def disconnect
|
54
56
|
@stream_connection.disconnect if @stream_connection
|
55
57
|
|
@@ -102,25 +104,20 @@ module Lightstreamer
|
|
102
104
|
|
103
105
|
private
|
104
106
|
|
105
|
-
|
106
|
-
|
107
|
-
@stream_connection.
|
107
|
+
def create_stream_connection
|
108
|
+
@stream_connection = StreamConnection.new self
|
109
|
+
@stream_connection.connect
|
108
110
|
end
|
109
111
|
|
110
|
-
# Attempts to parses the next line of data from the stream connection as a custom control address and then uses this
|
111
|
-
# address to create the control connection. Note that the control address is optional and if it is absent then
|
112
|
-
# {#server_url} will be used instead of a custom control address.
|
113
112
|
def create_control_connection
|
114
|
-
|
115
|
-
control_address = (match && match.captures.first) || server_url
|
116
|
-
|
117
|
-
# The rest of the contents in the header is ignored, so read up until the blank line that marks its ending
|
118
|
-
loop { break if @stream_connection.read_line == '' }
|
113
|
+
control_address = @stream_connection.control_address || server_url
|
119
114
|
|
120
|
-
# If the control
|
121
|
-
|
115
|
+
# If the control address doesn't have a schema then use the same schema as the server URL
|
116
|
+
unless control_address.start_with? 'http'
|
117
|
+
control_address = "#{URI(server_url).scheme}://#{control_address}"
|
118
|
+
end
|
122
119
|
|
123
|
-
@control_connection = ControlConnection.new @session_id, control_address
|
120
|
+
@control_connection = ControlConnection.new @stream_connection.session_id, control_address
|
124
121
|
end
|
125
122
|
|
126
123
|
# Starts the processing thread that reads and processes incoming data from the stream connection.
|
@@ -129,8 +126,14 @@ module Lightstreamer
|
|
129
126
|
loop do
|
130
127
|
line = @stream_connection.read_line
|
131
128
|
|
129
|
+
break if line.nil?
|
130
|
+
|
132
131
|
process_stream_data line unless line.empty?
|
133
132
|
end
|
133
|
+
|
134
|
+
# The stream connection has died, so exit the processing thread
|
135
|
+
warn "Lightstreamer: processing thread exiting, error: #{@stream_connection.error}"
|
136
|
+
@processing_thread = @stream_connection = @control_connection = nil
|
134
137
|
end
|
135
138
|
end
|
136
139
|
|
@@ -145,17 +148,5 @@ module Lightstreamer
|
|
145
148
|
|
146
149
|
warn "Lightstreamer: unprocessed stream data '#{line}'" unless was_processed
|
147
150
|
end
|
148
|
-
|
149
|
-
# Handles a failure to establish a stream connection by reading off the error code and error message then raising
|
150
|
-
# a {ProtocolError}.
|
151
|
-
def handle_connection_error
|
152
|
-
error_code = @stream_connection.read_line
|
153
|
-
error_message = @stream_connection.read_line
|
154
|
-
|
155
|
-
@stream_connection = nil
|
156
|
-
@control_connection = nil
|
157
|
-
|
158
|
-
raise ProtocolError.new(error_message, error_code)
|
159
|
-
end
|
160
151
|
end
|
161
152
|
end
|
@@ -5,6 +5,16 @@ module Lightstreamer
|
|
5
5
|
# @return [Thread] The thread used to process incoming streaming data.
|
6
6
|
attr_reader :thread
|
7
7
|
|
8
|
+
# @return [String] The session ID returned from the server when this stream connection was initiated.
|
9
|
+
attr_reader :session_id
|
10
|
+
|
11
|
+
# @return [String] The control address returned from the server when this stream connection was initiated.
|
12
|
+
attr_reader :control_address
|
13
|
+
|
14
|
+
# @return [ProtocolError, RequestError, String] If an error occurs on the stream thread that causes this stream
|
15
|
+
# to disconnect then the exception or error details will be stored in this attribute.
|
16
|
+
attr_reader :error
|
17
|
+
|
8
18
|
# Establishes a new stream connection using the authentication details from the passed session.
|
9
19
|
#
|
10
20
|
# @param [Session] session The session to create a stream connection for.
|
@@ -12,73 +22,128 @@ module Lightstreamer
|
|
12
22
|
@session = session
|
13
23
|
@queue = Queue.new
|
14
24
|
|
15
|
-
|
25
|
+
@stream_create_url = URI.join(session.server_url, '/lightstreamer/create_session.txt').to_s
|
26
|
+
@stream_bind_url = URI.join(session.server_url, '/lightstreamer/bind_session.txt').to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
# Establishes a new stream connection using the authentication details from the session that was passed to
|
30
|
+
# {#initialize}. Raises {ProtocolError} or {RequestError} on failure.
|
31
|
+
def connect
|
32
|
+
return if @thread
|
33
|
+
@session_id = @error = nil
|
34
|
+
|
16
35
|
create_stream_thread
|
36
|
+
|
37
|
+
# Wait until the connection result is known
|
38
|
+
until @session_id || @error
|
39
|
+
end
|
40
|
+
|
41
|
+
raise @error if @error
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns whether or not this stream connection is connected.
|
45
|
+
#
|
46
|
+
# @return [Boolean]
|
47
|
+
def connected?
|
48
|
+
!@thread.nil?
|
17
49
|
end
|
18
50
|
|
19
51
|
# Disconnects this stream connection by shutting down the streaming thread.
|
20
52
|
def disconnect
|
21
|
-
|
53
|
+
if @thread
|
54
|
+
Thread.kill @thread
|
55
|
+
@thread.join
|
56
|
+
end
|
22
57
|
|
23
|
-
|
24
|
-
@thread.join
|
58
|
+
@session_id = @control_address = @error = @thread = nil
|
25
59
|
end
|
26
60
|
|
27
|
-
# Reads the next line of streaming data.
|
61
|
+
# Reads the next line of streaming data. If the streaming thread is alive then this method blocks the calling thread
|
62
|
+
# until a line of data is available. If the streaming thread is not active then any unconsumed lines will be
|
63
|
+
# returned and after that the return value will be `nil`.
|
64
|
+
#
|
65
|
+
# @return [String, nil]
|
28
66
|
def read_line
|
67
|
+
return nil if @queue.empty? && @thread.nil?
|
68
|
+
|
29
69
|
@queue.pop
|
30
70
|
end
|
31
71
|
|
32
72
|
private
|
33
73
|
|
34
|
-
def create_stream
|
35
|
-
@stream = Net::HTTP.new stream_uri.host, stream_uri.port
|
36
|
-
@stream.use_ssl = true if stream_uri.port == 443
|
37
|
-
end
|
38
|
-
|
39
74
|
def create_stream_thread
|
40
75
|
@thread = Thread.new do
|
41
76
|
begin
|
42
|
-
|
77
|
+
stream_thread_main
|
43
78
|
rescue StandardError => error
|
44
|
-
|
45
|
-
exit 1
|
79
|
+
@error = error
|
46
80
|
end
|
81
|
+
|
82
|
+
@thread = nil
|
47
83
|
end
|
48
84
|
end
|
49
85
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
86
|
+
def stream_thread_main
|
87
|
+
connect_stream_and_process_data stream_create_post_request
|
88
|
+
|
89
|
+
while @loop
|
90
|
+
@loop = false
|
91
|
+
connect_stream_and_process_data stream_bind_post_request
|
53
92
|
end
|
54
93
|
end
|
55
94
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
95
|
+
def stream_create_post_request
|
96
|
+
params = { LS_op2: 'create', LS_cid: 'mgQkwtwdysogQz2BJ4Ji kOj2Bg', LS_user: @session.username,
|
97
|
+
LS_password: @session.password }
|
98
|
+
|
99
|
+
params[:LS_adapter_set] = @session.adapter_set if @session.adapter_set
|
100
|
+
|
101
|
+
Typhoeus::Request.new @stream_create_url, method: :post, params: params
|
102
|
+
end
|
103
|
+
|
104
|
+
def stream_bind_post_request
|
105
|
+
Typhoeus::Request.new @stream_bind_url, method: :post, params: { LS_session: @session_id }
|
106
|
+
end
|
107
|
+
|
108
|
+
def connect_stream_and_process_data(request)
|
109
|
+
@header = StreamConnectionHeader.new
|
110
|
+
|
111
|
+
buffer = LineBuffer.new
|
112
|
+
request.on_body do |data|
|
113
|
+
buffer.process data, &method(:process_stream_line)
|
114
|
+
end
|
115
|
+
|
116
|
+
request.on_complete do |response|
|
117
|
+
@error = RequestError.new(response.return_message, response.response_code) unless response.success?
|
64
118
|
end
|
119
|
+
|
120
|
+
request.run
|
65
121
|
end
|
66
122
|
|
67
|
-
def
|
68
|
-
|
123
|
+
def process_stream_line(line)
|
124
|
+
if @header
|
125
|
+
process_header_line line
|
126
|
+
else
|
127
|
+
process_body_line line
|
128
|
+
end
|
69
129
|
end
|
70
130
|
|
71
|
-
def
|
72
|
-
|
73
|
-
LS_op2: 'create',
|
74
|
-
LS_cid: 'mgQkwtwdysogQz2BJ4Ji kOj2Bg',
|
75
|
-
LS_user: @session.username,
|
76
|
-
LS_password: @session.password
|
77
|
-
}
|
131
|
+
def process_header_line(line)
|
132
|
+
return if @header.process_header_line line
|
78
133
|
|
79
|
-
|
134
|
+
@control_address = @header['ControlAddress']
|
135
|
+
@session_id = @header['SessionId']
|
136
|
+
@error = @header.error
|
137
|
+
|
138
|
+
@header = nil
|
139
|
+
end
|
80
140
|
|
81
|
-
|
141
|
+
def process_body_line(line)
|
142
|
+
if line == 'LOOP'
|
143
|
+
@loop = true
|
144
|
+
elsif !ignore_line?(line)
|
145
|
+
@queue.push line
|
146
|
+
end
|
82
147
|
end
|
83
148
|
|
84
149
|
def ignore_line?(line)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Lightstreamer
|
2
|
+
# Helper class that processes the contents of the header returned by the server when a new stream connection is
|
3
|
+
# created or an existing session is bound to.
|
4
|
+
class StreamConnectionHeader
|
5
|
+
# @return [ProtocolError, RequestError] If there was an error in the header then this value will be set to the
|
6
|
+
# error instance that should be raised in response.
|
7
|
+
attr_reader :error
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@data = {}
|
11
|
+
@lines = []
|
12
|
+
end
|
13
|
+
|
14
|
+
# Processes a single line of header information. The return value indicates whether further data is required in
|
15
|
+
# order to complete the header.
|
16
|
+
#
|
17
|
+
# @param [String] line The line of header data to process.
|
18
|
+
#
|
19
|
+
# @return [Boolean] Whether the header is still incomplete and requires further data.
|
20
|
+
def process_header_line(line)
|
21
|
+
@lines << line
|
22
|
+
|
23
|
+
unless %w(OK ERROR).include? @lines.first
|
24
|
+
@error = RequestError.new line
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
return true if @lines.first == 'OK' && !line.empty?
|
29
|
+
return true if @lines.first == 'ERROR' && @lines.size < 3
|
30
|
+
|
31
|
+
parse_header
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the value for the item with the specified name in this header, or `nil` if no item with the specified name
|
37
|
+
# was part of this header
|
38
|
+
#
|
39
|
+
# @param [String] item_name The name of the item to return the header value for.
|
40
|
+
#
|
41
|
+
# @return [String, nil] The value of the item as specified in this header, or `nil` if the item name was not
|
42
|
+
# specified in this header.
|
43
|
+
def [](item_name)
|
44
|
+
@data[item_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def parse_header
|
50
|
+
if @lines.first == 'OK'
|
51
|
+
@lines[1..-1].each do |line|
|
52
|
+
match = line.match(/^([^:]*):(.*)$/)
|
53
|
+
@data[match.captures[0]] = match.captures[1] if match
|
54
|
+
end
|
55
|
+
elsif @lines.first == 'ERROR'
|
56
|
+
@error = ProtocolError.new @lines[2], @lines[1]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -134,12 +134,8 @@ module Lightstreamer
|
|
134
134
|
|
135
135
|
private
|
136
136
|
|
137
|
-
# Attempts to parse
|
138
|
-
#
|
139
|
-
# @param [String] line The line of stream data to parse.
|
140
|
-
#
|
141
|
-
# @return [Array] The first value is the item index, and the second is a hash of the values contained in the stream
|
142
|
-
# data. Will be nil if the stream data was not able to be parsed.
|
137
|
+
# Attempts to parse a line of stream data. If parsing is successful then the first return value is the item index,
|
138
|
+
# and the second is a hash of the values contained in the stream data.
|
143
139
|
def parse_stream_data(line)
|
144
140
|
match = line.match stream_data_regex
|
145
141
|
return unless match
|
@@ -152,18 +148,12 @@ module Lightstreamer
|
|
152
148
|
|
153
149
|
# Returns the regular expression that will match a single line of data in the incoming stream that is relevant to
|
154
150
|
# this subscription. The ID at the beginning must match, as well as the number of fields.
|
155
|
-
#
|
156
|
-
# @return [Regexp]
|
157
151
|
def stream_data_regex
|
158
152
|
Regexp.new "^#{id},(\\d+)#{'\|(.*)' * fields.size}"
|
159
153
|
end
|
160
154
|
|
161
155
|
# Parses an array of values from an incoming line of stream data into a hash where the keys are the field names
|
162
156
|
# defined for this subscription.
|
163
|
-
#
|
164
|
-
# @param [String] values The raw values from the incoming stream data.
|
165
|
-
#
|
166
|
-
# @return [Hash] The parsed values as a hash where the keys are the field names of this subscription.
|
167
157
|
def parse_values(values)
|
168
158
|
hash = {}
|
169
159
|
|
@@ -183,10 +173,7 @@ module Lightstreamer
|
|
183
173
|
|
184
174
|
value = value[1..-1] if value =~ /^(\$|#)/
|
185
175
|
|
186
|
-
|
187
|
-
# '\uXXXX\uYYYY'. They need to be transformed into native Unicode characters.
|
188
|
-
|
189
|
-
value
|
176
|
+
UTF16.decode_escape_sequences value
|
190
177
|
end
|
191
178
|
|
192
179
|
# Invokes all of this subscription's data callbacks with the specified arguments. Any exceptions that occur in a
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Lightstreamer
|
2
|
+
# This module supports the decoding of UTF-16 escape sequences
|
3
|
+
module UTF16
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Decodes any UTF-16 escape sequences in the form '\uXXXX' into a new string. Invalid escape sequences are removed.
|
7
|
+
def decode_escape_sequences(string)
|
8
|
+
string = decode_surrogate_pairs_escape_sequences string
|
9
|
+
|
10
|
+
# Match all remaining escape sequences
|
11
|
+
string.gsub(/\\u[A-F\d]{4}/i) do |escape_sequence|
|
12
|
+
codepoint = escape_sequence[2..-1].hex
|
13
|
+
|
14
|
+
# Codepoints greater than 0xD7FF are invalid
|
15
|
+
codepoint < 0xD800 ? [codepoint].pack('U') : ''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Converts any UTF-16 surrogate pairs escape sequences in the form '\uXXXX\uYYYY' into UTF-8.
|
20
|
+
def decode_surrogate_pairs_escape_sequences(string)
|
21
|
+
string.gsub(/\\uD[89AB][A-F\d]{2}\\uD[C-F][A-F\d]{2}/i) do |escape_sequence|
|
22
|
+
high_surrogate = escape_sequence[2...6].hex
|
23
|
+
low_surrogate = escape_sequence[8...12].hex
|
24
|
+
|
25
|
+
codepoint = 0x10000 + ((high_surrogate - 0xD800) << 10) + (low_surrogate - 0xDC00)
|
26
|
+
|
27
|
+
[codepoint].pack 'U'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lightstreamer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.3'
|
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-07-
|
11
|
+
date: 2016-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: thor
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0.19'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0.19'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: typhoeus
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '1.1'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '1.1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: activesupport
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,7 +184,9 @@ files:
|
|
184
184
|
- lib/lightstreamer/request_error.rb
|
185
185
|
- lib/lightstreamer/session.rb
|
186
186
|
- lib/lightstreamer/stream_connection.rb
|
187
|
+
- lib/lightstreamer/stream_connection_header.rb
|
187
188
|
- lib/lightstreamer/subscription.rb
|
189
|
+
- lib/lightstreamer/utf16.rb
|
188
190
|
- lib/lightstreamer/version.rb
|
189
191
|
homepage: https://github.com/rviney/lightstreamer
|
190
192
|
licenses:
|