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_grip_channel_header(channels) click to toggle source

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_hold(mode, channels, response, timeout=nil) click to toggle source

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
create_hold_response(channels, response=nil, timeout=nil) click to toggle source

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
create_hold_stream(channels, response=nil) click to toggle source

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_websocket_events(body) click to toggle source

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_websocket_events(events) click to toggle source

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_grip_uri(uri) click to toggle source

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_sig(token, key) click to toggle source

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
websocket_control_message(type, args=nil) click to toggle source

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_hold_channels(channels) click to toggle source

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_hold_response(response) click to toggle source

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_channels(channels) click to toggle source

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