class GripControl
The GripControl
class provides functionality that is used in conjunction with GRIP proxies. This includes facilitating the creation of hold instructions for HTTP long-polling and HTTP streaming, parsing GRIP URIs into config objects, validating the GRIP-SIG header coming from GRIP proxies, creating GRIP channel headers, and also WebSocket-over-HTTP features such as encoding/decoding web socket events and generating control messages.
Public Class Methods
Create a GRIP channel header for the specified channels. The channels parameter can be specified as a string representing the channel name, a Channel
instance, or an array of Channel
instances. The returned GRIP channel header is used when sending instructions to GRIP proxies via HTTP headers.
# File lib/gripcontrol.rb, line 129 def self.create_grip_channel_header(channels) channels = parse_channels(channels) parts = [] channels.each do |channel| s = channel.name if !channel.prev_id.nil? s += '; prev-id=%s' % [channel.prev_id] end parts.push(s) end return parts.join(', ') end
Create GRIP hold instructions for the specified mode, channels, response and optional timeout value. The channel parameter can be specified as either a string representing the channel name, a Channel
instance or an array of Channel
instances. The response parameter can be specified as either a string representing the response body or a Response
instance.
# File lib/gripcontrol.rb, line 34 def self.create_hold(mode, channels, response, timeout=nil) hold = Hash.new hold['mode'] = mode channels = GripControl.parse_channels(channels) ichannels = GripControl.get_hold_channels(channels) hold['channels'] = ichannels if !timeout.nil? hold['timeout'] = timeout end iresponse = GripControl.get_hold_response(response) instruct = Hash.new instruct['hold'] = hold if !iresponse.nil? instruct['response'] = iresponse end return instruct.to_json end
A convenience method for creating GRIP hold response instructions for HTTP long-polling. This method simply passes the specified parameters to the create_hold
method with ‘response’ as the hold mode.
# File lib/gripcontrol.rb, line 145 def self.create_hold_response(channels, response=nil, timeout=nil) return GripControl.create_hold('response', channels, response, timeout) end
A convenience method for creating GRIP hold stream instructions for HTTP streaming. This method simply passes the specified parameters to the create_hold
method with ‘stream’ as the hold mode.
# File lib/gripcontrol.rb, line 152 def self.create_hold_stream(channels, response=nil) return create_hold('stream', channels, response) end
Decode the specified HTTP request body into an array of WebSocketEvent
instances when using the WebSocket-over-HTTP protocol. A RuntimeError is raised if the format is invalid.
# File lib/gripcontrol.rb, line 159 def self.decode_websocket_events(body) out = [] start = 0 while start < body.length do at = body.index("\r\n", start) if at.nil? raise 'bad format' end typeline = body[start..at - 1] start = at + 2 at = typeline.index(' ') event = nil if !at.nil? etype = typeline[0..at - 1] clen = ('0x' + typeline[at + 1..-1]).to_i(16) content = body[start..start + clen - 1] start += clen + 2 event = WebSocketEvent.new(etype, content) else event = WebSocketEvent.new(typeline) end out.push(event) end return out end
Encode the specified array of WebSocketEvent
instances. The returned string value should then be passed to a GRIP proxy in the body of an HTTP response when using the WebSocket-over-HTTP protocol.
# File lib/gripcontrol.rb, line 188 def self.encode_websocket_events(events) out = '' events.each do |event| if !event.content.nil? out += "%s %x\r\n%s\r\n" % [event.type, event.content.length, event.content] else out += "%s\r\n" % [event.type] end end return out end
Parse the specified GRIP URI into a config object that can then be passed to the GripPubControl
class. The URI can include ‘iss’ and ‘key’ JWT authentication query parameters as well as any other required query string parameters. The JWT ‘key’ query parameter can be provided as-is or in base64 encoded format.
# File lib/gripcontrol.rb, line 57 def self.parse_grip_uri(uri) uri = URI(uri) params = {} if (uri.query) params = CGI.parse(uri.query) end iss = nil key = nil if params.key?('iss') iss = params['iss'][0] params.delete('iss') end if params.key?('key') key = params['key'][0] params.delete('key') end if !key.nil? and key.start_with?('base64:') key = Base64.decode64(key[7..-1]) end qs = [] params.map do |name,values| values.map do |value| qs.push("#{CGI.escape name}=#{CGI.escape value}") end end qs = qs.join('&') path = uri.path if path.end_with?('/') path = path[0..-2] end port = '' if uri.port != 80 port = ':' + uri.port.to_s end control_uri = uri.scheme + '://' + uri.host + port + path if !qs.nil? and !qs.empty? control_uri += '?' + qs end out = {'control_uri' => control_uri} if !iss.nil? out['control_iss'] = iss end if !key.nil? out['key'] = key end return out end
Validate the specified JWT token and key. This method is used to validate the GRIP-SIG header coming from GRIP proxies such as Pushpin or Fanout.io. Note that the token expiration is also verified.
# File lib/gripcontrol.rb, line 108 def self.validate_sig(token, key) token = token.encode('utf-8') begin claim = JWT.decode(token, key, true, {verify_expiration: false}) rescue return false end if claim.length == 0 or !claim[0].key?('exp') return false end if Time.now.utc.to_i >= claim[0]['exp'] return false end return true end
Generate a WebSocket control message with the specified type and optional arguments. WebSocket control messages are passed to GRIP proxies and example usage includes subscribing/unsubscribing a WebSocket connection to/from a channel.
# File lib/gripcontrol.rb, line 205 def self.websocket_control_message(type, args=nil) if !args.nil? out = Marshal.load(Marshal.dump(args)) else out = Hash.new end out['type'] = type return out.to_json end
Private Class Methods
Get an array of hashes representing the specified channels parameter. The resulting array is used for creating GRIP proxy hold instructions.
# File lib/gripcontrol.rb, line 232 def self.get_hold_channels(channels) ichannels = [] channels.each do |channel| if channel.is_a?(String) channel = Channel(channel) end ichannel = Hash.new ichannel['name'] = channel.name if !channel.prev_id.nil? ichannel['prev-id'] = channel.prev_id end ichannels.push(ichannel) end return ichannels end
Get a hash representing the specified response parameter. The resulting hash is used for creating GRIP proxy hold instructions.
# File lib/gripcontrol.rb, line 250 def self.get_hold_response(response) iresponse = nil if !response.nil? if response.is_a?(String) response = Response.new(nil, nil, nil, response) end iresponse = Hash.new if !response.code.nil? iresponse['code'] = response.code end if !response.reason.nil? iresponse['reason'] = response.reason end if !response.headers.nil? and response.headers.length > 0 iresponse['headers'] = response.headers end if !response.body.nil? if response.body.clone.force_encoding("UTF-8").valid_encoding? iresponse['body'] = response.body else iresponse['body-bin'] = Base64.encode64(response.body) end end end return iresponse end
Parse the specified parameter into an array of Channel
instances. The specified parameter can either be a string, a Channel
instance, or an array of Channel
instances.
# File lib/gripcontrol.rb, line 220 def self.parse_channels(channels) if channels.is_a?(Channel) channels = [channels] elsif channels.is_a?(String) channels = [Channel.new(channels)] end raise 'channels.length equal to 0' unless channels.length > 0 return channels end