Class: GoogleMaps::Services::GoogleClient

Inherits:
Object
  • Object
show all
Includes:
Exceptions
Defined in:
lib/googlemaps/services/client.rb

Overview

Performs requests to the Google Maps API web services.

Since:

  • 1.0.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key: nil, client_id: nil, client_secret: nil, write_timeout: 2, connect_timeout: 5, read_timeout: 10, retry_timeout: 60, request_headers: {}, queries_per_second: 50, channel: nil, retry_over_query_limit: true, response_format: :json) ⇒ GoogleClient

Returns a new instance of GoogleClient.

Since:

  • 1.0.0



50
51
52
53
54
55
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
# File 'lib/googlemaps/services/client.rb', line 50

def initialize(key: nil, client_id: nil, client_secret: nil, write_timeout: 2,
               connect_timeout: 5, read_timeout: 10, retry_timeout: 60, request_headers: {},
               queries_per_second: 50, channel: nil, retry_over_query_limit: true, response_format: :json)
  unless key || (client_secret && client_id)
    raise StandardError, 'Must provide API key or enterprise credentials when creating client.'
  end

  if key && !key.start_with?('AIza')
    raise StandardError, 'Invalid API key provided.'
  end

  if channel
    raise StandardError, 'The channel argument must be used with a client ID.' unless client_id

    unless /^[a-zA-Z0-9._-]*$/.match(channel)
      raise StandardError, 'The channel argument must be an ASCII alphanumeric string. The period (.), underscore (_) and hyphen (-) characters are allowed.'
    end
  end

  self.key = key

  # Set the timeout for write/connect/read calls
  self.write_timeout = write_timeout
  self.connect_timeout = connect_timeout
  self.read_timeout = read_timeout

  self.client_id = client_id
  self.client_secret = client_secret
  self.channel = channel
  self.retry_timeout = retry_timeout
  self.request_headers = request_headers.merge({'User-Agent' => Constants::USER_AGENT})
  self.queries_per_second = queries_per_second
  self.sent_times = Array.new
  self.retry_over_query_limit = retry_over_query_limit

  if response_format
    raise StandardError, 'Unsupported response format. Should be either :json or :xml.' unless [:json, :xml].include? response_format
    self.response_format = response_format
  end
end

Instance Attribute Details

#channelSymbol

Returns Attribute used for tracking purposes. Can only be used with a Client ID.

Returns:

  • (Symbol)

    Attribute used for tracking purposes. Can only be used with a Client ID.

Since:

  • 1.0.0



36
37
38
# File 'lib/googlemaps/services/client.rb', line 36

def channel
  @channel
end

#client_idSymbol

Returns Client ID (for Maps API for Work).

Returns:

  • (Symbol)

    Client ID (for Maps API for Work).

Since:

  • 1.0.0



32
33
34
# File 'lib/googlemaps/services/client.rb', line 32

def client_id
  @client_id
end

#client_secretSymbol

Returns Base64-encoded client secret (for Maps API for Work).

Returns:

  • (Symbol)

    Base64-encoded client secret (for Maps API for Work).

Since:

  • 1.0.0



34
35
36
# File 'lib/googlemaps/services/client.rb', line 34

def client_secret
  @client_secret
end

#connect_timeoutSymbol

Returns Connect timeout for the HTTP request, in seconds.

Returns:

  • (Symbol)

    Connect timeout for the HTTP request, in seconds.

Since:

  • 1.0.0



28
29
30
# File 'lib/googlemaps/services/client.rb', line 28

def connect_timeout
  @connect_timeout
end

#keySymbol

Returns API key. Required, unless “client_id” and “client_secret” are set.

Returns:

  • (Symbol)

    API key. Required, unless “client_id” and “client_secret” are set.

Since:

  • 1.0.0



24
25
26
# File 'lib/googlemaps/services/client.rb', line 24

def key
  @key
end

#queries_per_secondSymbol

Returns 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.

Returns:

  • (Symbol)

    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.

Since:

  • 1.0.0



42
43
44
# File 'lib/googlemaps/services/client.rb', line 42

def queries_per_second
  @queries_per_second
end

#read_timeoutSymbol

Returns Read timeout for the HTTP request, in seconds.

Returns:

  • (Symbol)

    Read timeout for the HTTP request, in seconds.

Since:

  • 1.0.0



30
31
32
# File 'lib/googlemaps/services/client.rb', line 30

def read_timeout
  @read_timeout
end

#request_headersSymbol

Returns HTTP headers per request.

Returns:

  • (Symbol)

    HTTP headers per request.

Since:

  • 1.0.0



40
41
42
# File 'lib/googlemaps/services/client.rb', line 40

def request_headers
  @request_headers
end

#response_formatSymbol

Returns Response format. Either :json or :xml.

Returns:

  • (Symbol)

    Response format. Either :json or :xml

Since:

  • 1.0.0



48
49
50
# File 'lib/googlemaps/services/client.rb', line 48

def response_format
  @response_format
end

#retry_over_query_limitSymbol

Returns Should retry request when exceeds the query rate limit.

Returns:

  • (Symbol)

    Should retry request when exceeds the query rate limit.

Since:

  • 1.0.0



46
47
48
# File 'lib/googlemaps/services/client.rb', line 46

def retry_over_query_limit
  @retry_over_query_limit
end

#retry_timeoutSymbol

Returns Timeout across multiple retriable requests, in seconds.

Returns:

  • (Symbol)

    Timeout across multiple retriable requests, in seconds.

Since:

  • 1.0.0



38
39
40
# File 'lib/googlemaps/services/client.rb', line 38

def retry_timeout
  @retry_timeout
end

#sent_timesSymbol

Returns keeps track of sent queries.

Returns:

  • (Symbol)

    keeps track of sent queries.

Since:

  • 1.0.0



44
45
46
# File 'lib/googlemaps/services/client.rb', line 44

def sent_times
  @sent_times
end

#write_timeoutSymbol

Returns Write timeout for the HTTP request, in seconds.

Returns:

  • (Symbol)

    Write timeout for the HTTP request, in seconds.

Since:

  • 1.0.0



26
27
28
# File 'lib/googlemaps/services/client.rb', line 26

def write_timeout
  @write_timeout
end

Instance Method Details

#request(url:, params:, first_request_time: nil, retry_counter: 0, base_url: Constants::DEFAULT_BASE_URL, accepts_clientid: true, extract_body: nil, request_headers: nil, post_json: nil) ⇒ Hash, ...

Performs HTTP GET/POST requests with credentials, returning the body as JSON or XML.

Parameters:

  • url (String)

    URL path for the request. Should begin with a slash.

  • params (Hash)

    HTTP GET parameters.

  • first_request_time (Time) (defaults to: nil)

    The time of the first request (nil if no retries have occurred).

  • retry_counter (Integer) (defaults to: 0)

    The number of this retry, or zero for first attempt.

  • base_url (String) (defaults to: Constants::DEFAULT_BASE_URL)

    The base URL for the request. Defaults to the Google Maps API server. Should not have a trailing slash.

  • accepts_clientid (TrueClass, FalseClass) (defaults to: true)

    Flag whether this call supports the client/signature params. Some APIs require API keys (e.g. Roads).

  • extract_body (Proc) (defaults to: nil)

    A function that extracts the body from the request. If the request was not successful, the function should raise a GoogleMaps::Services::Exceptions::HTTPError or GoogleMaps::Services::Exceptions::APIError as appropriate.

  • request_headers (Hash) (defaults to: nil)

    HTTP headers per request.

  • post_json (Hash) (defaults to: nil)

    The request body which will be formatted as JSON.

Returns:

  • (Hash, Array, nil)

    response body (either in JSON or XML) or nil.

Since:

  • 1.0.0



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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/googlemaps/services/client.rb', line 105

def request(url:, params:, first_request_time: nil, retry_counter: 0, base_url: Constants::DEFAULT_BASE_URL,
            accepts_clientid: true, extract_body: nil, request_headers: nil, post_json: nil)
  first_request_time = Util.current_time unless first_request_time

  elapsed = Time.now - first_request_time
  if elapsed > self.retry_timeout
    raise Timeout
  end

  if retry_counter && retry_counter > 0
    # 0.5 * (1.5 ^ i) is an increased sleep time of 1.5x per iteration,
    # starting at 0.5s when retry_counter=0. The first retry will occur
    # at 1, so subtract that first.
    delay_seconds = 0.5 * 1.5 ** (retry_counter - 1)
    # Jitter this value by 50% and pause.
    sleep(delay_seconds * (Random.rand + 0.5))
  end

  authed_url = generate_auth_url(url, params, accepts_clientid)

  # Default to the client-level self.request_headers, with method-level
  # request_headers arg overriding.
  request_headers = self.request_headers.merge(request_headers || {})

  # Construct the Request URI
  uri = HTTP::URI.parse(base_url + authed_url)

  # Create the request, add the headers & timeouts
  req = HTTP.headers(request_headers)
            .timeout(:write => self.write_timeout, :connect => self.connect_timeout, :read => self.read_timeout)

  # Make the HTTP GET/POST request
  resp = post_json ? req.post(uri.to_s, :json => post_json) : req.get(uri.to_s)

  if Constants::RETRIABLE_STATUSES.include? resp.code.to_i
    # Retry request
    self.request(url: url, params: params, first_request_time: first_request_time, retry_counter: retry_counter + 1,
                 base_url: base_url, accepts_clientid: accepts_clientid, extract_body: extract_body,
                 request_headers: request_headers, post_json: post_json)
  end

  # Check if the time of the nth previous query (where n is queries_per_second)
  # is under a second ago - if so, sleep for the difference.
  if self.sent_times && (self.sent_times.length == self.queries_per_second)
    elapsed_since_earliest = Util.current_time - self.sent_times[0]
    if elapsed_since_earliest < 1
      sleep(1 - elapsed_since_earliest)
    end
  end

  begin
    # Extract HTTP response body
    if extract_body
      result = extract_body.call(resp)
    else
      case resp.content_type.mime_type
      when 'application/xml'
        result = get_xml_body(resp)
      when 'application/json'
        result = get_json_body(resp)
      when 'text/html'
        result = get_redirection_url(resp)
      else
        result = get_map_image(resp)
      end
    end
    self.sent_times.push(Util.current_time)
    return result
  rescue RetriableRequest => e
    if e.is_a?(OverQueryLimit) && !self.retry_over_query_limit
      raise
    end
    # Retry request
    self.request(url: url, params: params, first_request_time: first_request_time, retry_counter: retry_counter + 1,
                 base_url: base_url, accepts_clientid: accepts_clientid, extract_body: extract_body,
                 request_headers: request_headers, post_json: post_json)
  end
end