Class: RestConnection::Connection

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_yaml = File.join(File.expand_path("~"), ".rest_connection", "rest_api_config.yaml")) ⇒ Connection

RestConnection api settings configuration file: Settings are loaded from a yaml configuration file in users home directory. Copy the example config from the gemhome/config/rest_api_config.yaml.sample to ~/.rest_connection/rest_api_config.yaml OR to /etc/rest_connection/rest_api_config.yaml Here’s an example of overriding the settings in the configuration file:

Server.connection.settings[:api_url] = "https://my.rightscale.com/api/acct/1234"


56
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
# File 'lib/rest_connection.rb', line 56

def initialize(config_yaml = File.join(File.expand_path("~"), ".rest_connection", "rest_api_config.yaml"))
  @@logger = nil
  @@user = nil
  @@pass = nil
  etc_config = File.join("#{File::SEPARATOR}etc", "rest_connection", "rest_api_config.yaml")
  app_bin_dir = File.expand_path(File.dirname(caller.last))
  app_yaml = File.join(app_bin_dir,"..","config","rest_api_config.yaml")
  if config_yaml.is_a?(Hash)
    @settings = config_yaml
  elsif File.exists?(app_yaml)
    @settings = YAML::load(IO.read(app_yaml))
  elsif File.exists?(config_yaml)
    @settings = YAML::load(IO.read(config_yaml))
  elsif File.exists?(etc_config)
    @settings = YAML::load(IO.read(etc_config))
  else
    logger("\nWARNING:  you must setup config file rest_api_config.yaml in #{app_yaml} or #{config_yaml} or #{etc_config}")
    logger("WARNING:  see GEM_HOME/rest_connection/config/rest_api_config.yaml for example config")
    @settings = {}
  end
  @settings.keys.each { |k| @settings[k.to_sym] = @settings[k] if String === k }

  @settings[:extension] = ".js"
  @settings[:api_href] = @settings[:api_url] unless @settings[:api_href]
  unless @settings[:user]
    @@user = ask("Username:") unless @@user
    @settings[:user] = @@user
  end
  unless @settings[:pass]
    @@pass = ask("Password:") { |q| q.echo = false } unless @@pass
    @settings[:pass] = @@pass
  end
  @settings[:azure_hack_on] = true if @settings[:azure_hack_on] == nil
  @settings[:azure_hack_retry_count] ||= 5
  @settings[:azure_hack_sleep_seconds] ||= 60
  @settings[:api_logging] ||= false
  @settings[:legacy_shard] = true if @settings[:legacy_shard] == nil
end

Instance Attribute Details

Settings is a hash of options for customizing the connection. settings.merge! { :common_headers => { “X_CUSTOM_HEADER” => “BLAH” }, :api_url => :user => :pass =>



47
48
49
# File 'lib/rest_connection.rb', line 47

def cookie
  @cookie
end

#settingsObject

Settings is a hash of options for customizing the connection. settings.merge! { :common_headers => { “X_CUSTOM_HEADER” => “BLAH” }, :api_url => :user => :pass =>



47
48
49
# File 'lib/rest_connection.rb', line 47

def settings
  @settings
end

Instance Method Details

#delete(href, additional_parameters = {}) ⇒ Object

connection.delete(server_url)

href = “/api/base_new” if this begins with a slash then the url will be used as absolute path. href = “servers” this will be concat’d on to the api_url from the settings additional_parameters = Hash or String of parameters to pass to HTTP::Delete



213
214
215
216
217
218
219
220
221
222
# File 'lib/rest_connection.rb', line 213

def delete(href, additional_parameters = {})
  rest_connect do |base_uri, headers|
    new_href = (href =~ /^\// ? href : "#{base_uri}/#{href}")
    new_path = URI.escape(new_href)
    req = Net::HTTP::Delete.new(href, headers)
    req.set_content_type('application/json')
    req.body = additional_parameters.to_json
    req
  end
end

#error_handler(e) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rest_connection.rb', line 134

def error_handler(e)
  case e
  when EOFError, Timeout::Error
    if @max_retries > 0
      logger("Caught #{e}. Retrying...")
      @max_retries -= 1
      return true
    end
  when RestConnection::Errors::Forbidden
    if @max_retries > 0
      if e.response.body =~ /(session|cookie).*(invalid|expired)/i
        logger("Caught '#{e.response.body}'. Refreshing cookie...")
        refresh_cookie if respond_to?(:refresh_cookie)
      else
        return false
      end
      @max_retries -= 1
      return true
    end
  end
  return false
end

#get(href, additional_parameters = "") ⇒ Object

connection.get(“/root/login”, :test_header => “x”, :test_header2 => “y”) href = “/api/base_new” if this begins with a slash then the url will be used as absolute path. href = “servers” this will be concat’d on to the api_url from the settings additional_parameters = Hash or String of parameters to pass to HTTP::Get



161
162
163
164
165
166
167
168
169
# File 'lib/rest_connection.rb', line 161

def get(href, additional_parameters = "")
  rest_connect do |base_uri,headers|
    new_href = (href =~ /^\// ? href : "#{base_uri}/#{href}")
    puts("DEBUG: new_href get : #{new_href.inspect}") if @settings[:api_logging]
    params = requestify(additional_parameters) || ""
    new_path = URI.escape(new_href + @settings[:extension] + "?") + params
    Net::HTTP::Get.new(new_path, headers)
  end
end

#handle_response(res) ⇒ Object

handle_response res = HTTP response

decoding and post processing goes here. This is where you may need some customization if you want to handle the response differently (or not at all!). Luckily it’s easy to modify based on this handler.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/rest_connection.rb', line 228

def handle_response(res)
  if res.code.to_i == 201 or res.code.to_i == 202
    #
    # In most cases, it's safe to return the location
    #
  	if res['Location']
  	  return res['Location']
  	else
  	  #
  	  # Ec2ServerArrayInternal.run_script_on_instances returns XML.
  	  # We need to parse it to retrieve the href's to audit entries.
  	  #
      xml_response = Nokogiri::XML(res.body)
      return xml_response.xpath('audit-entries/audit-entry/href').map { |href| href.content }
    end
  elsif [200,203,204].detect { |d| d == res.code.to_i }

    if res.body
      begin
        return JSON.load(res.body)
      rescue => e
        return res
      end
    else
      return res
    end
  else
    raise RestConnection::Errors.status_error(res)
  end
end

#logger(message) ⇒ Object

Logs a message at info level to stdout or log file FIXME: don’t lazy initialize



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/rest_connection.rb', line 261

def logger(message)
  init_message = "Initializing Logging using "
  if @@logger.nil?
    if ENV['REST_CONNECTION_LOG']
      @@logger = Logger.new(ENV['REST_CONNECTION_LOG'])
      init_message += ENV['REST_CONNECTION_LOG']
    else
      @@logger = Logger.new(STDOUT)
      init_message += "STDOUT"
    end
    @@logger.info(init_message)
  end

  if @settings.nil?
    @@logger.info(message)
  else
    @@logger.info("[API v#{@settings[:common_headers]['X_API_VERSION']}] " + message)
  end
end

#name_with_prefix(prefix, name) ⇒ Object

used by requestify to build parameters strings



282
283
284
# File 'lib/rest_connection.rb', line 282

def name_with_prefix(prefix, name)
  prefix ? "#{prefix}[#{name}]" : name.to_s
end

#post(href, additional_parameters = {}) ⇒ Object

connection.post(server_url + “/start”)

href = “/api/base_new” if this begins with a slash then the url will be used as absolute path. href = “servers” this will be concat’d on to the api_url from the settings additional_parameters = Hash or String of parameters to pass to HTTP::Post



176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/rest_connection.rb', line 176

def post(href, additional_parameters = {})
  rest_connect do |base_uri, headers|
    new_href = (href =~ /^\// ? href : "#{base_uri}/#{href}")
    puts("DEBUG: new_href post : #{new_href.inspect}") if @settings[:api_logging]
    res = Net::HTTP::Post.new(new_href , headers)
    unless additional_parameters.empty?
      res.set_content_type('application/json')
      res.body = additional_parameters.to_json
    end
    #res.set_form_data(additional_parameters, '&')
    res
  end
end

#put(href, additional_parameters = {}) ⇒ Object

connection.put(server_url + “/start”)

href = “/api/base” if this begins with a slash then the url will be used as absolute path. href = “servers” this will be concat’d on to the api_url from the settings additional_parameters = Hash or String of parameters to pass to HTTP::Put



195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/rest_connection.rb', line 195

def put(href, additional_parameters = {})
  rest_connect do |base_uri, headers|
    new_href = (href =~ /^\// ? href : "#{base_uri}/#{href}")
    new_path = URI.escape(new_href)
    puts("DEBUG: new_href put : #{new_href.inspect}") if @settings[:api_logging]
    req = Net::HTTP::Put.new(new_path, headers)
    puts("DEBUG: req  put : #{req.inspect}") if @settings[:api_logging]
    req.set_content_type('application/json')
    req.body = additional_parameters.to_json
    req
  end
end

#requestify(parameters, prefix = nil) ⇒ Object

recursive method builds CGI escaped strings from Hashes, Arrays and strings of parameters.



287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/rest_connection.rb', line 287

def requestify(parameters, prefix=nil)
  if Hash === parameters
    return nil if parameters.empty?
    parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
  elsif Array === parameters
    parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
  elsif prefix.nil?
    parameters
  else
    "#{prefix}=#{CGI.escape(parameters.to_s)}"
  end
end

#rest_connect(&block) ⇒ Object

Main HTTP connection loop. Common settings are set here, then we yield(BASE_URI, OPTIONAL_HEADERS) to other methods for each type of HTTP request: GET, PUT, POST, DELETE

The block must return a Net::HTTP Request. You have a chance to taylor the request inside the block that you pass by modifying the url and headers.

rest_connect do |base_uri, headers|

headers.merge! {:my_header => "blah"}
Net::HTTP::Get.new(base_uri, headers)

end



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/rest_connection.rb', line 104

def rest_connect(&block)
  uri = URI.parse(@settings[:api_href])
  http = Net::HTTP.new(uri.host, uri.port)
  http.read_timeout = 300
  if uri.scheme == 'https'
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end
  headers = @settings[:common_headers]
  http.start do |http|
    @max_retries = 3
    ret = nil
    begin
      headers.delete("Cookie")
      headers.merge!("Cookie" => @cookie) if @cookie
      req = yield(uri, headers)
      logger("#{req.method}: #{req.path}")
      logger("\trequest body: #{req.body}") if req.body and req.body !~ /password/
      req.basic_auth(@settings[:user], @settings[:pass]) if @settings[:user] unless @cookie

      response, body = http.request(req)
      ret = handle_response(response)
    rescue Exception => e
      raise unless error_handler(e)
      retry
    end
    ret
  end
end