class GoogleMapsService::Client

Core client functionality, common across all API requests (including performing HTTP requests).

Constants

DEFAULT_BASE_URL

Default Google Maps Web Service base endpoints

RETRIABLE_ERRORS

Errors those could be retriable.

Attributes

client_id[RW]

Client id for using Maps API for Work services. @return [String]

client_secret[RW]

Client secret for using Maps API for Work services. @return [String]

key[RW]

Secret key for accessing Google Maps Web Service. Can be obtained at developers.google.com/maps/documentation/geocoding/get-api-key#key. @return [String]

queries_per_second[R]

Number of queries per second permitted. If the rate limit is reached, the client will sleep for the appropriate amount of time before it runs the current query. @return [Integer]

retry_timeout[RW]

Timeout across multiple retriable requests, in seconds. @return [Integer]

Public Class Methods

new(**options) click to toggle source

Construct Google Maps Web Service API client.

This gem uses [Hurley](github.com/lostisland/hurley) as internal HTTP client. You can configure `Hurley::Client` through `request_options` and `ssl_options` parameters. You can also directly get the `Hurley::Client` object via {#client} method.

@example Setup API keys

gmaps = GoogleMapsService::Client.new(key: 'Add your key here')

@example Setup client IDs

gmaps = GoogleMapsService::Client.new(
    client_id: 'Add your client id here',
    client_secret: 'Add your client secret here'
)

@example Setup time out and QPS limit

gmaps = GoogleMapsService::Client.new(
    key: 'Add your key here',
    retry_timeout: 20,
    queries_per_second: 10
)

@example Request behind proxy

request_options = Hurley::RequestOptions.new
request_options.proxy = Hurley::Url.parse 'http://user:password@proxy.example.com:3128'

gmaps = GoogleMapsService::Client.new(
    key: 'Add your key here',
    request_options: request_options
)

@example Using Excon and Http Cache

require 'hurley-excon'       # https://github.com/lostisland/hurley-excon
require 'hurley/http_cache'  # https://github.com/plataformatec/hurley-http-cache

gmaps = GoogleMapsService::Client.new(
    key: 'Add your key here',
    connection: Hurley::HttpCache.new(HurleyExcon::Connection.new)
)

@option options [String] :key Secret key for accessing Google Maps Web Service.

Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key.

@option options [String] :client_id Client id for using Maps API for Work services. @option options [String] :client_secret Client secret for using Maps API for Work services. @option options [Integer] :retry_timeout Timeout across multiple retriable requests, in seconds. @option options [Integer] :queries_per_second Number of queries per second permitted.

@option options [Hurley::RequestOptions] :request_options HTTP client request options.

See https://github.com/lostisland/hurley/blob/master/lib/hurley/options.rb.

@option options [Hurley::SslOptions] :ssl_options HTTP client SSL options.

See https://github.com/lostisland/hurley/blob/master/lib/hurley/options.rb.

@option options [Object] :connection HTTP client connection.

By default, the default Hurley's HTTP client connection (Net::Http) will be used.
See https://github.com/lostisland/hurley/blob/master/README.md#connections.
# File lib/google_maps_service/client.rb, line 111
def initialize(**options)
  [:key, :client_id, :client_secret,
      :retry_timeout, :queries_per_second,
      :request_options, :ssl_options, :connection].each do |key|
    self.instance_variable_set("@#{key}".to_sym, options[key] || GoogleMapsService.instance_variable_get("@#{key}"))
  end

  initialize_query_tickets
end

Public Instance Methods

client() click to toggle source

Get the current HTTP client. @return [Hurley::Client]

# File lib/google_maps_service/client.rb, line 123
def client
  @client ||= new_client
end

Protected Instance Methods

check_body_error(response, body) click to toggle source

Check response body for error status.

@param [Hurley::Response] response Response object. @param [Hash] body Response body.

@return [void]

# File lib/google_maps_service/client.rb, line 274
def check_body_error(response, body)
  case body[:status]
  when 'OK', 'ZERO_RESULTS'
    # Do-nothing
  when 'OVER_QUERY_LIMIT'
    raise GoogleMapsService::Error::RateLimitError.new(response), body[:error_message]
  when 'REQUEST_DENIED'
    raise GoogleMapsService::Error::RequestDeniedError.new(response), body[:error_message]
  when 'INVALID_REQUEST'
    raise GoogleMapsService::Error::InvalidRequestError.new(response), body[:error_message]
  else
    raise GoogleMapsService::Error::ApiError.new(response), body[:error_message]
  end
end
check_response_status_code(response) click to toggle source

Check HTTP response status code. Raise error if the status is not 2xx.

@param [Hurley::Response] response Web API response.

# File lib/google_maps_service/client.rb, line 253
def check_response_status_code(response)
  case response.status_code
  when 200..300
    # Do-nothing
  when 301, 302, 303, 307
    raise GoogleMapsService::Error::RedirectError.new(response), sprintf('Redirect to %s', response.header[:location])
  when 401
    raise GoogleMapsService::Error::ClientError.new(response), 'Unauthorized'
  when 304, 400, 402...500
    raise GoogleMapsService::Error::ClientError.new(response), 'Invalid request'
  when 500..600
    raise GoogleMapsService::Error::ServerError.new(response), 'Server error'
  end
end
decode_response_body(response) click to toggle source

Extract and parse body response as hash. Throw an error if there is something wrong with the response.

@param [Hurley::Response] response Web API response.

@return [Hash] Response body as hash. The hash key will be symbolized.

# File lib/google_maps_service/client.rb, line 243
def decode_response_body(response)
  check_response_status_code(response)
  body = MultiJson.load(response.body, :symbolize_keys => true)
  check_body_error(response, body)
  body
end
generate_auth_url(path, params, accepts_client_id) click to toggle source

Returns the path and query string portion of the request URL, first adding any necessary parameters.

@param [String] path The path portion of the URL. @param [Hash] params URL parameters. @param [Boolean] accepts_client_id Sign the request using API {#keys} instead of {#client_id}.

@return [String]

# File lib/google_maps_service/client.rb, line 213
def generate_auth_url(path, params, accepts_client_id)
  # Deterministic ordering through sorting by key.
  # Useful for tests, and in the future, any caching.
  if params.kind_of?(Hash)
    params = params.sort
  else
    params = params.dup
  end

  if accepts_client_id and @client_id and @client_secret
    params << ["client", @client_id]

    path = [path, GoogleMapsService::Url.urlencode_params(params)].join("?")
    sig = GoogleMapsService::Url.sign_hmac(@client_secret, path)
    return path + "&signature=" + sig
  end

  if @key
    params << ["key", @key]
    return path + "?" + GoogleMapsService::Url.urlencode_params(params)
  end

  raise ArgumentError, "Must provide API key for this API. It does not accept enterprise credentials."
end
get(path, params, base_url: DEFAULT_BASE_URL, accepts_client_id: true, custom_response_decoder: nil) click to toggle source

Make API call.

@param [String] path Url path. @param [String] params Request parameters. @param [String] base_url Base Google Maps Web Service API endpoint url. @param [Boolean] accepts_client_id Sign the request using API {#keys} instead of {#client_id}. @param [Method] custom_response_decoder Custom method to decode raw API response.

@return [Object] Decoded response body.

# File lib/google_maps_service/client.rb, line 171
def get(path, params, base_url: DEFAULT_BASE_URL, accepts_client_id: true, custom_response_decoder: nil)
  url = base_url + generate_auth_url(path, params, accepts_client_id)

  Retriable.retriable timeout: @retry_timeout, on: RETRIABLE_ERRORS do |try|
    begin
      request_query_ticket
      response = client.get url
    ensure
      release_query_ticket
    end

    return custom_response_decoder.call(response) if custom_response_decoder
    decode_response_body(response)
  end
end
initialize_query_tickets() click to toggle source

Initialize QPS queue. QPS queue is a “tickets” for calling API

# File lib/google_maps_service/client.rb, line 130
def initialize_query_tickets
  if @queries_per_second
    @qps_queue = SizedQueue.new @queries_per_second
    @queries_per_second.times do
      @qps_queue << 0
    end
  end
end
new_client() click to toggle source

Create a new HTTP client. @return [Hurley::Client]

# File lib/google_maps_service/client.rb, line 141
def new_client
  client = Hurley::Client.new
  client.request_options.query_class = Hurley::Query::Flat
  client.request_options.redirection_limit = 0
  client.header[:user_agent] = user_agent

  client.connection = @connection if @connection
  @request_options.each_pair {|key, value| client.request_options[key] = value } if @request_options
  @ssl_options.each_pair {|key, value| client.ssl_options[key] = value } if @ssl_options

  client
end
release_query_ticket() click to toggle source

Release request “ticket”.

@return [void]

# File lib/google_maps_service/client.rb, line 201
def release_query_ticket
  @qps_queue << Time.now if @qps_queue
end
request_query_ticket() click to toggle source

Get/wait the request “ticket” if QPS is configured. Check for previous request time, it must be more than a second ago before calling new request.

@return [void]

# File lib/google_maps_service/client.rb, line 191
def request_query_ticket
  if @qps_queue
    elapsed_since_earliest = Time.now - @qps_queue.pop
    sleep(1 - elapsed_since_earliest) if elapsed_since_earliest.to_f < 1
  end
end
user_agent() click to toggle source

Build the user agent header @return [String]

# File lib/google_maps_service/client.rb, line 156
def user_agent
  sprintf('google-maps-services-ruby/%s %s',
          GoogleMapsService::VERSION,
          GoogleMapsService::OS_VERSION)
end