gripcontrol 1.2.1 → 1.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cae2f828396f43ecec0430ca4dec5ed6bb3781a6
4
- data.tar.gz: b232a252079b157d246c9d33f409511a1d6d3bc4
3
+ metadata.gz: 58499f9d93b611ba458fbdeab01a6e124f22d944
4
+ data.tar.gz: a56f77a99c922f9399d4b926eccea80d7e108305
5
5
  SHA512:
6
- metadata.gz: 82727271f42df23c2758106b287364d0522afa37eb8697dce5d9da997ee311fcda9b18d06192618be998ab25962ead3fd499eb4ecc930a017217ba4dbc02cdb5
7
- data.tar.gz: 5f83386f017dd97bc2337176c0b596d3ee201dc5721ca8a7ebebfdd2956aa9dfa234b440fe45152ad12dbc410bc099c05759e36f1d0fab1937568f5d0ee5282b
6
+ metadata.gz: ee47b91038d0645aec69a8b41123737df2e18866f700dc8474217ed097738a22e3757ebc945812091a6db9caec1b927017f148e45a0bd48042b9ff5d9f318e8e
7
+ data.tar.gz: dc032e0a364295699fa43049edfe498a08b476812161ea8a314f3c507b4a21045b47b04131b7129ed25aa75fb547e60c77219765314cd1045bc31d617b842948
data/lib/channel.rb CHANGED
@@ -5,10 +5,13 @@
5
5
  # :copyright: (c) 2015 by Fanout, Inc.
6
6
  # :license: MIT, see LICENSE for more details.
7
7
 
8
+ # The Channel class is used to represent a channel in for a GRIP proxy and
9
+ # tracks the previous ID of the last message.
8
10
  class Channel
9
11
  attr_accessor :name
10
12
  attr_accessor :prev_id
11
13
 
14
+ # Initialize with the channel name and an optional previous ID.
12
15
  def initialize(name, prev_id=nil)
13
16
  @name = name
14
17
  @prev_id = prev_id
data/lib/gripcontrol.rb CHANGED
@@ -17,55 +17,30 @@ require_relative 'websocketevent.rb'
17
17
  require_relative 'grippubcontrol.rb'
18
18
  require_relative 'response.rb'
19
19
 
20
+ # The GripControl class provides functionality that is used in conjunction
21
+ # with GRIP proxies. This includes facilitating the creation of hold
22
+ # instructions for HTTP long-polling and HTTP streaming, parsing GRIP URIs
23
+ # into config objects, validating the GRIP-SIG header coming from GRIP
24
+ # proxies, creating GRIP channel headers, and also WebSocket-over-HTTP
25
+ # features such as encoding/decoding web socket events and generating
26
+ # control messages.
20
27
  class GripControl
28
+
29
+ # Create GRIP hold instructions for the specified mode, channels, response
30
+ # and optional timeout value. The channel parameter can be specified as
31
+ # either a string representing the channel name, a Channel instance or an
32
+ # array of Channel instances. The response parameter can be specified as
33
+ # either a string representing the response body or a Response instance.
21
34
  def self.create_hold(mode, channels, response, timeout=nil)
22
35
  hold = Hash.new
23
36
  hold['mode'] = mode
24
- if channels.is_a?(Channel)
25
- channels = [channels]
26
- elsif channels.is_a?(String)
27
- channels = [Channel.new(channels)]
28
- end
29
- raise 'channels.length equal to 0' unless channels.length > 0
30
- ichannels = []
31
- channels.each do |channel|
32
- if channel.is_a?(String)
33
- channel = Channel(channel)
34
- end
35
- ichannel = Hash.new
36
- ichannel['name'] = channel.name
37
- if !channel.prev_id.nil?
38
- ichannel['prev-id'] = channel.prev_id
39
- end
40
- ichannels.push(ichannel)
41
- end
37
+ channels = GripControl.parse_channels(channels)
38
+ ichannels = GripControl.get_hold_channels(channels)
42
39
  hold['channels'] = ichannels
43
40
  if !timeout.nil?
44
41
  hold['timeout'] = timeout
45
42
  end
46
- iresponse = nil
47
- if !response.nil?
48
- if response.is_a?(String)
49
- response = Response(nil, nil, nil, response)
50
- end
51
- iresponse = Hash.new
52
- if !response.code.nil?
53
- iresponse['code'] = response.code
54
- end
55
- if !response.reason.nil?
56
- iresponse['reason'] = response.reason
57
- end
58
- if !response.headers.nil? and response.headers.length > 0
59
- iresponse['headers'] = response.headers
60
- end
61
- if !response.body.nil?
62
- if response.body.encoding.name == 'ASCII-8BIT'
63
- iresponse['body-bin'] = Base64.encode64(response.body)
64
- else
65
- iresponse['body'] = response.body
66
- end
67
- end
68
- end
43
+ iresponse = GripControl.get_hold_response(response)
69
44
  instruct = Hash.new
70
45
  instruct['hold'] = hold
71
46
  if !iresponse.nil?
@@ -74,9 +49,17 @@ class GripControl
74
49
  return instruct.to_json
75
50
  end
76
51
 
52
+ # Parse the specified GRIP URI into a config object that can then be passed
53
+ # to the GripPubControl class. The URI can include 'iss' and 'key' JWT
54
+ # authentication query parameters as well as any other required query string
55
+ # parameters. The JWT 'key' query parameter can be provided as-is or in base64
56
+ # encoded format.
77
57
  def self.parse_grip_uri(uri)
78
58
  uri = URI(uri)
79
- params = CGI.parse(uri.query)
59
+ params = {}
60
+ if (uri.query)
61
+ params = CGI.parse(uri.query)
62
+ end
80
63
  iss = nil
81
64
  key = nil
82
65
  if params.key?('iss')
@@ -93,7 +76,7 @@ class GripControl
93
76
  qs = []
94
77
  params.map do |name,values|
95
78
  values.map do |value|
96
- qs.push('#{CGI.escape name}=#{CGI.escape value}')
79
+ qs.push("#{CGI.escape name}=#{CGI.escape value}")
97
80
  end
98
81
  end
99
82
  qs = qs.join('&')
@@ -119,6 +102,9 @@ class GripControl
119
102
  return out
120
103
  end
121
104
 
105
+ # Validate the specified JWT token and key. This method is used to validate
106
+ # the GRIP-SIG header coming from GRIP proxies such as Pushpin or Fanout.io.
107
+ # Note that the token expiration is also verified.
122
108
  def self.validate_sig(token, key)
123
109
  token = token.encode('utf-8')
124
110
  begin
@@ -135,13 +121,13 @@ class GripControl
135
121
  return true
136
122
  end
137
123
 
124
+ # Create a GRIP channel header for the specified channels. The channels
125
+ # parameter can be specified as a string representing the channel name,
126
+ # a Channel instance, or an array of Channel instances. The returned GRIP
127
+ # channel header is used when sending instructions to GRIP proxies via
128
+ # HTTP headers.
138
129
  def self.create_grip_channel_header(channels)
139
- if channels.is_a?(Channel)
140
- channels = [channels]
141
- elsif channels.is_a?(String)
142
- channels = [Channel.new(channels)]
143
- end
144
- raise 'channels.length equal to 0' unless channels.length > 0
130
+ channels = parse_channels(channels)
145
131
  parts = []
146
132
  channels.each do |channel|
147
133
  s = channel.name
@@ -153,14 +139,23 @@ class GripControl
153
139
  return parts.join(', ')
154
140
  end
155
141
 
142
+ # A convenience method for creating GRIP hold response instructions for HTTP
143
+ # long-polling. This method simply passes the specified parameters to the
144
+ # create_hold method with 'response' as the hold mode.
156
145
  def self.create_hold_response(channels, response=nil, timeout=nil)
157
146
  return GripControl.create_hold('response', channels, response, timeout)
158
147
  end
159
148
 
149
+ # A convenience method for creating GRIP hold stream instructions for HTTP
150
+ # streaming. This method simply passes the specified parameters to the
151
+ # create_hold method with 'stream' as the hold mode.
160
152
  def self.create_hold_stream(channels, response=nil)
161
153
  return create_hold('stream', channels, response)
162
154
  end
163
155
 
156
+ # Decode the specified HTTP request body into an array of WebSocketEvent
157
+ # instances when using the WebSocket-over-HTTP protocol. A RuntimeError
158
+ # is raised if the format is invalid.
164
159
  def self.decode_websocket_events(body)
165
160
  out = []
166
161
  start = 0
@@ -187,6 +182,9 @@ class GripControl
187
182
  return out
188
183
  end
189
184
 
185
+ # Encode the specified array of WebSocketEvent instances. The returned string
186
+ # value should then be passed to a GRIP proxy in the body of an HTTP response
187
+ # when using the WebSocket-over-HTTP protocol.
190
188
  def self.encode_websocket_events(events)
191
189
  out = ''
192
190
  events.each do |event|
@@ -200,6 +198,10 @@ class GripControl
200
198
  return out
201
199
  end
202
200
 
201
+ # Generate a WebSocket control message with the specified type and optional
202
+ # arguments. WebSocket control messages are passed to GRIP proxies and
203
+ # example usage includes subscribing/unsubscribing a WebSocket connection
204
+ # to/from a channel.
203
205
  def self.websocket_control_message(type, args=nil)
204
206
  if !args.nil?
205
207
  out = Marshal.load(Marshal.dump(args))
@@ -209,4 +211,66 @@ class GripControl
209
211
  out['type'] = type
210
212
  return out.to_json
211
213
  end
214
+
215
+ private
216
+
217
+ # Parse the specified parameter into an array of Channel instances. The
218
+ # specified parameter can either be a string, a Channel instance, or
219
+ # an array of Channel instances.
220
+ def self.parse_channels(channels)
221
+ if channels.is_a?(Channel)
222
+ channels = [channels]
223
+ elsif channels.is_a?(String)
224
+ channels = [Channel.new(channels)]
225
+ end
226
+ raise 'channels.length equal to 0' unless channels.length > 0
227
+ return channels
228
+ end
229
+
230
+ # Get an array of hashes representing the specified channels parameter. The
231
+ # resulting array is used for creating GRIP proxy hold instructions.
232
+ def self.get_hold_channels(channels)
233
+ ichannels = []
234
+ channels.each do |channel|
235
+ if channel.is_a?(String)
236
+ channel = Channel(channel)
237
+ end
238
+ ichannel = Hash.new
239
+ ichannel['name'] = channel.name
240
+ if !channel.prev_id.nil?
241
+ ichannel['prev-id'] = channel.prev_id
242
+ end
243
+ ichannels.push(ichannel)
244
+ end
245
+ return ichannels
246
+ end
247
+
248
+ # Get a hash representing the specified response parameter. The
249
+ # resulting hash is used for creating GRIP proxy hold instructions.
250
+ def self.get_hold_response(response)
251
+ iresponse = nil
252
+ if !response.nil?
253
+ if response.is_a?(String)
254
+ response = Response.new(nil, nil, nil, response)
255
+ end
256
+ iresponse = Hash.new
257
+ if !response.code.nil?
258
+ iresponse['code'] = response.code
259
+ end
260
+ if !response.reason.nil?
261
+ iresponse['reason'] = response.reason
262
+ end
263
+ if !response.headers.nil? and response.headers.length > 0
264
+ iresponse['headers'] = response.headers
265
+ end
266
+ if !response.body.nil?
267
+ if response.body.clone.force_encoding("UTF-8").valid_encoding?
268
+ iresponse['body'] = response.body
269
+ else
270
+ iresponse['body-bin'] = Base64.encode64(response.body)
271
+ end
272
+ end
273
+ end
274
+ return iresponse
275
+ end
212
276
  end
@@ -7,11 +7,19 @@
7
7
 
8
8
  require 'pubcontrol'
9
9
 
10
+ # The GripPubControl class allows consumers to easily publish HTTP response
11
+ # and HTTP stream format messages to GRIP proxies. Configuring GripPubControl
12
+ # is slightly different from configuring PubControl in that the 'uri' and
13
+ # 'iss' keys in each config entry should have a 'control_' prefix.
14
+ # GripPubControl inherits from PubControl and therefore also provides all
15
+ # of the same functionality.
10
16
  class GripPubControl < PubControl
11
17
  alias super_add_client add_client
12
18
  alias super_publish publish
13
19
  alias super_publish_async publish_async
14
20
 
21
+ # Initialize with or without a configuration. A configuration can be applied
22
+ # after initialization via the apply_grip_config method.
15
23
  def initialize(config=nil)
16
24
  @clients = Array.new
17
25
  if !config.nil?
@@ -19,6 +27,11 @@ class GripPubControl < PubControl
19
27
  end
20
28
  end
21
29
 
30
+ # Apply the specified configuration to this GripPubControl instance. The
31
+ # configuration object can either be a hash or an array of hashes where
32
+ # each hash corresponds to a single PubControlClient instance. Each hash
33
+ # will be parsed and a PubControlClient will be created either using just
34
+ # a URI or a URI and JWT authentication information.
22
35
  def apply_grip_config(config)
23
36
  if !config.is_a?(Array)
24
37
  config = [config]
@@ -35,6 +48,12 @@ class GripPubControl < PubControl
35
48
  end
36
49
  end
37
50
 
51
+ # Synchronously publish an HTTP response format message to all of the
52
+ # configured PubControlClients with a specified channel, message, and
53
+ # optional ID and previous ID. Note that the 'http_response' parameter can
54
+ # be provided as either an HttpResponseFormat instance or a string (in which
55
+ # case an HttpResponseFormat instance will automatically be created and
56
+ # have the 'body' field set to the specified string).
38
57
  def publish_http_response(channel, http_response, id=nil, prev_id=nil)
39
58
  if http_response.is_a?(String)
40
59
  http_response = HttpResponseFormat.new(nil, nil, nil, http_response)
@@ -43,6 +62,14 @@ class GripPubControl < PubControl
43
62
  super_publish(channel, item)
44
63
  end
45
64
 
65
+ # Asynchronously publish an HTTP response format message to all of the
66
+ # configured PubControlClients with a specified channel, message, and
67
+ # optional ID, previous ID, and callback. Note that the 'http_response'
68
+ # parameter can be provided as either an HttpResponseFormat instance or
69
+ # a string (in which case an HttpResponseFormat instance will automatically
70
+ # be created and have the 'body' field set to the specified string). When
71
+ # specified, the callback method will be called after publishing is complete
72
+ # and passed a result and error message (if an error was encountered).
46
73
  def publish_http_response_async(channel, http_response, id=nil,
47
74
  prev_id=nil, callback=nil)
48
75
  if http_response.is_a?(String)
@@ -52,6 +79,12 @@ class GripPubControl < PubControl
52
79
  super_publish_async(channel, item, callback)
53
80
  end
54
81
 
82
+ # Synchronously publish an HTTP stream format message to all of the
83
+ # configured PubControlClients with a specified channel, message, and
84
+ # optional ID and previous ID. Note that the 'http_stream' parameter can
85
+ # be provided as either an HttpStreamFormat instance or a string (in which
86
+ # case an HttStreamFormat instance will automatically be created and
87
+ # have the 'content' field set to the specified string).
55
88
  def publish_http_stream(channel, http_stream, id=nil, prev_id=nil)
56
89
  if http_stream.is_a?(String)
57
90
  http_stream = HttpStreamFormat.new(http_stream)
@@ -60,6 +93,14 @@ class GripPubControl < PubControl
60
93
  super_publish(channel, item)
61
94
  end
62
95
 
96
+ # Asynchronously publish an HTTP stream format message to all of the
97
+ # configured PubControlClients with a specified channel, message, and
98
+ # optional ID, previous ID, and callback. Note that the 'http_stream'
99
+ # parameter can be provided as either an HttpStreamFormat instance or
100
+ # a string (in which case an HttpStreamFormat instance will automatically
101
+ # be created and have the 'content' field set to the specified string). When
102
+ # specified, the callback method will be called after publishing is complete
103
+ # and passed a result and error message (if an error was encountered).
63
104
  def publish_http_stream_async(channel, http_stream, id=nil,
64
105
  prev_id=nil, callback=nil)
65
106
  if http_stream.is_a?(String)
@@ -8,12 +8,16 @@
8
8
  require 'base64'
9
9
  require 'pubcontrol'
10
10
 
11
+ # The HttpResponseFormat class is the format used to publish messages to
12
+ # HTTP response clients connected to a GRIP proxy.
11
13
  class HttpResponseFormat < Format
12
14
  attr_accessor :code
13
15
  attr_accessor :reason
14
16
  attr_accessor :headers
15
17
  attr_accessor :body
16
18
 
19
+ # Initialize with the message code, reason, headers, and body to send
20
+ # to the client when the message is publishing.
17
21
  def initialize(code=nil, reason=nil, headers=nil, body=nil)
18
22
  @code = code
19
23
  @reason = reason
@@ -21,10 +25,14 @@ class HttpResponseFormat < Format
21
25
  @body = body
22
26
  end
23
27
 
28
+ # The name used when publishing this format.
24
29
  def name
25
30
  return 'http-response'
26
31
  end
27
32
 
33
+ # Export the message into the required format and include only the fields
34
+ # that are set. The body is exported as base64 if the text is encoded as
35
+ # binary.
28
36
  def export
29
37
  out = Hash.new
30
38
  if !@code.nil?
@@ -37,10 +45,10 @@ class HttpResponseFormat < Format
37
45
  out['headers'] = @headers
38
46
  end
39
47
  if !@body.nil?
40
- if @body.encoding.name == 'ASCII-8BIT'
41
- out['body-bin'] = Base64.encode64(@body)
42
- else
48
+ if @body.clone.force_encoding("UTF-8").valid_encoding?
43
49
  out['body'] = @body
50
+ else
51
+ out['body-bin'] = Base64.encode64(@body)
44
52
  end
45
53
  end
46
54
  return out
@@ -8,10 +8,15 @@
8
8
  require 'base64'
9
9
  require 'pubcontrol'
10
10
 
11
+ # The HttpStreamFormat class is the format used to publish messages to
12
+ # HTTP stream clients connected to a GRIP proxy.
11
13
  class HttpStreamFormat < Format
12
14
  attr_accessor :content
13
15
  attr_accessor :close
14
16
 
17
+ # Initialize with either the message content or a boolean indicating that
18
+ # the streaming connection should be closed. If neither the content nor
19
+ # the boolean flag is set then an error will be raised.
15
20
  def initialize(content=nil, close=false)
16
21
  @content = content
17
22
  @close = close
@@ -20,19 +25,23 @@ class HttpStreamFormat < Format
20
25
  end
21
26
  end
22
27
 
28
+ # The name used when publishing this format.
23
29
  def name
24
30
  return 'http-stream'
25
31
  end
26
32
 
33
+ # Exports the message in the required format depending on whether the
34
+ # message content is binary or not, or whether the connection should
35
+ # be closed.
27
36
  def export
28
37
  out = Hash.new
29
38
  if @close
30
39
  out['action'] = 'close'
31
40
  else
32
- if @content.encoding.name == 'ASCII-8BIT'
33
- out['content-bin'] = Base64.encode64(@content)
34
- else
41
+ if @content.clone.force_encoding("UTF-8").valid_encoding?
35
42
  out['content'] = @content
43
+ else
44
+ out['content-bin'] = Base64.encode64(@content)
36
45
  end
37
46
  end
38
47
  return out
data/lib/response.rb CHANGED
@@ -5,12 +5,18 @@
5
5
  # :copyright: (c) 2015 by Fanout, Inc.
6
6
  # :license: MIT, see LICENSE for more details.
7
7
 
8
+ # The Response class is used to represent a set of HTTP response data.
9
+ # Populated instances of this class are serialized to JSON and passed
10
+ # to the GRIP proxy in the body. The GRIP proxy then parses the message
11
+ # and deserialized the JSON into an HTTP response that is passed back
12
+ # to the client.
8
13
  class Response
9
14
  attr_accessor :code
10
15
  attr_accessor :reason
11
16
  attr_accessor :headers
12
17
  attr_accessor :body
13
18
 
19
+ # Initialize with an HTTP response code, reason, headers, and body.
14
20
  def initialize(code=nil, reason=nil, headers=nil, body=nil)
15
21
  @code = code
16
22
  @reason = reason
@@ -5,10 +5,14 @@
5
5
  # :copyright: (c) 2015 by Fanout, Inc.
6
6
  # :license: MIT, see LICENSE for more details.
7
7
 
8
+ # The WebSocketEvent class represents WebSocket event information that is
9
+ # used with the GRIP WebSocket-over-HTTP protocol. It includes information
10
+ # about the type of event as well as an optional content field.
8
11
  class WebSocketEvent
9
12
  attr_accessor :type
10
13
  attr_accessor :content
11
14
 
15
+ # Initialize with a specified event type and optional content information.
12
16
  def initialize(type, content=nil)
13
17
  @type = type
14
18
  @content = content
@@ -8,18 +8,25 @@
8
8
  require 'base64'
9
9
  require 'pubcontrol'
10
10
 
11
+ # The WebSocketMessageFormat class is the format used to publish data to
12
+ # WebSocket clients connected to GRIP proxies.
11
13
  class WebSocketMessageFormat < Format
12
14
  attr_accessor :content
13
15
 
16
+ # Initialize with the message content and a flag indicating whether the
17
+ # message content should be sent as base64-encoded binary data.
14
18
  def initialize(content, binary=false)
15
19
  @content = content
16
20
  @binary = binary
17
21
  end
18
22
 
23
+ # The name used when publishing this format.
19
24
  def name
20
25
  return 'ws-message'
21
26
  end
22
27
 
28
+ # Exports the message in the required format depending on whether the
29
+ # message content is binary or not.
23
30
  def export
24
31
  out = Hash.new
25
32
  if @binary
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gripcontrol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Bokarius
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-18 00:00:00.000000000 Z
11
+ date: 2015-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pubcontrol