Class: RightScale::BalancedHttpClient

Inherits:
Object
  • Object
show all
Defined in:
lib/right_agent/clients/balanced_http_client.rb

Overview

HTTP REST client for request-balanced access to RightScale servers Requests can be made using the EventMachine asynchronous HTTP interface in an efficient i/o non-blocking fashion using fibers or they can be made using the RestClient interface; either way they are synchronous to the client For the non-blocking i/o approach this class must be used from a spawned fiber rather than the root fiber This class is intended for use by instance agents and by infrastructure servers and therefore supports both session cookie and global session-based authentication

Defined Under Namespace

Classes: NotResponding

Constant Summary collapse

RETRY_STATUS_CODES =

HTTP status codes for which a retry is warranted, which is limited to when server is not accessible for some reason (408, 502, 503) or server response indicates that the request could not be routed for some retryable reason (504)

[408, 502, 503, 504]
DEFAULT_OPEN_TIMEOUT =

Default time for HTTP connection to open

2
HEALTH_CHECK_TIMEOUT =

Time to wait for health check response

5
DEFAULT_REQUEST_TIMEOUT =

Default time to wait for response from request

30
CONNECTION_REUSE_TIMEOUT =

Maximum time between uses of an HTTP connection

5
DEFAULT_HEALTH_CHECK_PATH =

Default health check path

"/health-check"
FILTERED_PARAM_VALUE =

Text used for filtered parameter value

"<hidden>"
CONTENT_FILTERED_PARAMS =

Parameters whose contents are also to have filtering applied

["payload"]
PROXY_ENVIRONMENT_VARIABLES =

Environment variables to examine for proxy settings, in order

['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY']

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(urls, options = {}) ⇒ BalancedHttpClient

Create client for making HTTP REST requests

Parameters:

  • urls (Array, String)

    of server being accessed as array or comma-separated string

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :api_version (String)

    for X-API-Version header

  • :server_name (String)

    of server for use in exceptions; defaults to host name

  • :health_check_path (String)

    in URI for health check resource; defaults to DEFAULT_HEALTH_CHECK_PATH

  • :filter_params (Array)

    symbols or strings for names of request parameters whose values are to be hidden when logging; also applied to contents of any parameters in CONTENT_FILTERED_PARAMS; can be augmented on individual requests

  • :non_blocking (Boolean)

    i/o is to be used for HTTP requests by applying EM::HttpRequest and fibers instead of RestClient; requests remain synchronous



81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/right_agent/clients/balanced_http_client.rb', line 81

def initialize(urls, options = {})
  @urls = split(urls)
  @api_version = options[:api_version]
  @server_name = options[:server_name]
  @filter_params = (options[:filter_params] || []).map { |p| p.to_s }

  # Create appropriate underlying HTTP client
  @http_client = options[:non_blocking] ? NonBlockingClient.new(options) : BlockingClient.new(options)

  # Initialize health check and its use in request balancer
  balancer_options = {:policy => RightSupport::Net::LB::HealthCheck, :health_check => @http_client.health_check_proc }
  @balancer = RightSupport::Net::RequestBalancer.new(@urls, balancer_options)
end

Class Method Details

.exception_text(exception) ⇒ String

Extract text of exception for logging For RestClient exceptions extract useful info from http_body attribute

Parameters:

  • exception (Exception, String, NilClass)

    or failure message

Returns:

  • (String)

    exception text



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/right_agent/clients/balanced_http_client.rb', line 465

def self.exception_text(exception)
  case exception
  when String
    exception
  when HttpException, RestClient::Exception
    if exception.http_body.nil? || exception.http_body.empty? || exception.http_body =~ /^<html>| html /
      exception.message
    else
      exception.inspect
    end
  when RightSupport::Net::NoResult, NotResponding
    "#{exception.class}: #{exception.message}"
  when Exception
    backtrace = exception.backtrace ? " in\n" + exception.backtrace.join("\n") : ""
    "#{exception.class}: #{exception.message}" + backtrace
  else
    ""
  end
end

.format(params) ⇒ String

Format query parameters for inclusion in URI It can only handle parameters that can be converted to a string or arrays of same, not hashes or arrays/hashes that recursively contain arrays and/or hashes

Parameters:

  • params (Hash)

    Parameters that are converted to <key>=<escaped_value> format and any value that is an array has each of its values formatted as <key>[]=<escaped_value>

Returns:

  • (String)

    Formatted parameter string with parameters separated by ‘&’



416
417
418
419
420
421
422
423
424
425
426
# File 'lib/right_agent/clients/balanced_http_client.rb', line 416

def self.format(params)
  p = []
  params.each do |k, v|
    if v.is_a?(Array)
      v.each { |v2| p << "#{k.to_s}[]=#{CGI.escape(v2.to_s)}" }
    else
      p << "#{k.to_s}=#{CGI.escape(v.to_s)}"
    end
  end
  p.join("&")
end

.response(code, body, headers, decode) ⇒ Object

Process HTTP response to produce result for client Extract result from location header for 201 response JSON-decode body of other 2xx responses except for 204 Raise exception if request failed

Parameters:

  • code (Integer)

    for response status

  • body (Object)

    of response

  • headers (Hash)

    for response

  • decode (Boolean)

    JSON-encoded body on success

Returns:

  • (Object)

    JSON-decoded response body

Raises:



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/right_agent/clients/balanced_http_client.rb', line 441

def self.response(code, body, headers, decode)
  if (200..207).include?(code)
    if code == 201
      result = headers[:location]
    elsif code == 204 || body.nil? || (body.respond_to?(:empty?) && body.empty?)
      result = nil
    elsif decode
      result = JSON.legacy_load(body)
      result = nil if result.respond_to?(:empty?) && result.empty?
    else
      result = body
    end
  else
    raise HttpExceptions.create(code, body, headers)
  end
  result
end

Instance Method Details

#check_health(host = nil) ⇒ Object

Check health of server

Parameters:

  • host (String) (defaults to: nil)

    name of server

Returns:

  • (Object)

    health check result from server

Raises:



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/right_agent/clients/balanced_http_client.rb', line 102

def check_health(host = nil)
  begin
    @http_client.health_check_proc.call(host || @urls.first)
  rescue StandardError => e
    if e.respond_to?(:http_code) && RETRY_STATUS_CODES.include?(e.http_code)
      raise NotResponding.new("#{@server_name || host} not responding", e)
    else
      raise
    end
  end
end

#close(reason) ⇒ TrueClass

Close all persistent connections

Parameters:

  • reason (String)

    for closing

Returns:

  • (TrueClass)

    always true



195
196
197
198
# File 'lib/right_agent/clients/balanced_http_client.rb', line 195

def close(reason)
  @http_client.close(reason) if @http_client
  true
end

#delete(*args) ⇒ Object



130
131
132
# File 'lib/right_agent/clients/balanced_http_client.rb', line 130

def delete(*args)
  request(:delete, *args)
end

#get(*args) ⇒ Object



114
115
116
# File 'lib/right_agent/clients/balanced_http_client.rb', line 114

def get(*args)
  request(:get, *args)
end

#poll(*args) ⇒ Object



126
127
128
# File 'lib/right_agent/clients/balanced_http_client.rb', line 126

def poll(*args)
  request(:poll, *args)
end

#post(*args) ⇒ Object



118
119
120
# File 'lib/right_agent/clients/balanced_http_client.rb', line 118

def post(*args)
  request(:post, *args)
end

#put(*args) ⇒ Object



122
123
124
# File 'lib/right_agent/clients/balanced_http_client.rb', line 122

def put(*args)
  request(:put, *args)
end

#request(verb, path, params = {}, options = {}) ⇒ Object

Make HTTP request If polling, continue to poll until receive data, timeout, or hit error Encode request parameters and response using JSON Apply configured authorization scheme Log request/response with filtered parameters included for failure or debug mode

Parameters:

  • verb (Symbol)

    for HTTP REST request

  • path (String)

    in URI for desired resource

  • params (Hash) (defaults to: {})

    for HTTP request

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :open_timeout (Numeric)

    maximum wait for connection; defaults to DEFAULT_OPEN_TIMEOUT

  • :request_timeout (Numeric)

    maximum wait for response; defaults to DEFAULT_REQUEST_TIMEOUT

  • :request_uuid (String)

    uniquely identifying request; defaults to random generated UUID

  • :filter_params (Array)

    symbols or strings for names of request parameters whose values are to be hidden when logging in addition to the ones provided during object initialization; also applied to contents of any parameters named :payload

  • :headers (Hash)

    to be added to request

  • :poll_timeout (Numeric)

    maximum wait for individual poll; defaults to :request_timeout

  • :log_level (Symbol)

    to use when logging information about the request other than errors; defaults to :info

Returns:

  • (Object)

    result returned by receiver of request

Raises:



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
184
185
186
187
188
# File 'lib/right_agent/clients/balanced_http_client.rb', line 159

def request(verb, path, params = {}, options = {})
  started_at = Time.now
  filter = @filter_params + (options[:filter_params] || []).map { |p| p.to_s }
  log_level = options[:log_level] || :info
  request_uuid = options[:request_uuid] || RightSupport::Data::UUID.generate
  connect_options, request_options = @http_client.options(verb, path, params, request_headers(request_uuid, options), options)

  Log.send(log_level, "Requesting #{verb.to_s.upcase} <#{request_uuid}> " + log_text(path, params, filter))

  used = {}
  result, code, body, headers = if verb != :poll
    rest_request(verb, path, connect_options, request_options, used)
  else
    poll_request(path, connect_options, request_options, options[:request_timeout], started_at, used)
  end

  log_success(result, code, body, headers, used[:host], path, request_uuid, started_at, log_level)
  result
rescue RightSupport::Net::NoResult => e
  handle_no_result(e, used[:host]) do |e2|
    log_failure(used[:host], path, params, filter, request_uuid, started_at, e2)
  end
rescue RestClient::Exception => e
  e2 = HttpExceptions.convert(e)
  log_failure(used[:host], path, params, filter, request_uuid, started_at, e2)
  raise e2
rescue StandardError => e
  log_failure(used[:host], path, params, filter, request_uuid, started_at, e)
  raise
end