Class: GripControl

Inherits:
Object
  • Object
show all
Defined in:
lib/gripcontrol.rb

Overview

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.

Class Method Summary collapse

Class Method Details

.create_grip_channel_header(channels) ⇒ Object

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.



129
130
131
132
133
134
135
136
137
138
139
140
# 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) ⇒ Object

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.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 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) ⇒ Object

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.



145
146
147
# 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) ⇒ Object

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.



152
153
154
# 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) ⇒ Object

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.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# 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) ⇒ Object

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.



188
189
190
191
192
193
194
195
196
197
198
199
# 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) ⇒ Object

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.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# 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) ⇒ Object

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.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# 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) ⇒ Object

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.



205
206
207
208
209
210
211
212
213
# 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