22require 'hurley'
33require 'multi_json'
44require 'retriable'
5+ require 'thread'
56
67module GoogleMapsService
78 class Client
@@ -43,13 +44,28 @@ class Client
4344 # @return [Integer]
4445 attr_reader :retry_timeout
4546
47+ # Number of queries per second permitted.
48+ # If the rate limit is reached, the client will sleep for
49+ # the appropriate amount of time before it runs the current query.
50+ # @return [Integer]
51+ attr_reader :queries_per_second
52+
4653 def initialize ( options = { } )
4754 @key = options [ :key ] || GoogleMapsService . key
4855 @client_id = options [ :client_id ] || GoogleMapsService . client_id
4956 @client_secret = options [ :client_secret ] || GoogleMapsService . client_secret
5057 @connect_timeout = options [ :connect_timeout ] || GoogleMapsService . connect_timeout
5158 @read_timeout = options [ :read_timeout ] || GoogleMapsService . read_timeout
5259 @retry_timeout = options [ :retry_timeout ] || GoogleMapsService . retry_timeout || 60
60+ @queries_per_second = options [ :queries_per_second ] || GoogleMapsService . queries_per_second
61+
62+ # Prepare "tickets" for calling API
63+ if @queries_per_second
64+ @sent_times = SizedQueue . new @queries_per_second
65+ @queries_per_second . times do
66+ @sent_times << 0
67+ end
68+ end
5369 end
5470
5571 # Get the current HTTP client
@@ -74,13 +90,21 @@ def new_client
7490 def get ( path , params , base_url : DEFAULT_BASE_URL , accepts_client_id : true , custom_response_decoder : nil )
7591 url = base_url + generate_auth_url ( path , params , accepts_client_id )
7692
77- Retriable . retriable timeout : @retry_timeout ,
78- on : RETRIABLE_ERRORS do |try |
79- response = client . get url
80- if custom_response_decoder
81- return custom_response_decoder . call ( response )
93+ Retriable . retriable timeout : @retry_timeout , on : RETRIABLE_ERRORS do |try |
94+ # Get/wait the request "ticket" if QPS is configured
95+ # Check for previous request time, it must be more than a second ago before calling new request
96+ if @sent_times
97+ elapsed_since_earliest = Time . now - @sent_times . pop
98+ sleep ( 1 - elapsed_since_earliest ) if elapsed_since_earliest . to_f < 1
8299 end
83- return decode_response_body ( response )
100+
101+ response = client . get url
102+
103+ # Release request "ticket"
104+ @sent_times << Time . now if @sent_times
105+
106+ return custom_response_decoder . call ( response ) if custom_response_decoder
107+ decode_response_body ( response )
84108 end
85109 end
86110
@@ -102,27 +126,17 @@ def decode_response_body(response)
102126
103127 body = MultiJson . load ( response . body , :symbolize_keys => true )
104128
105- api_status = body [ :status ]
106- if api_status == "OK" or api_status == " ZERO_RESULTS"
129+ case body [ :status ]
130+ when 'OK' , ' ZERO_RESULTS'
107131 return body
108- end
109-
110- if api_status == "OVER_QUERY_LIMIT"
132+ when 'OVER_QUERY_LIMIT'
111133 raise GoogleMapsService ::Error ::RateLimitError . new ( response ) , body [ :error_message ]
112- end
113-
114- if api_status == "REQUEST_DENIED"
134+ when 'REQUEST_DENIED'
115135 raise GoogleMapsService ::Error ::RequestDeniedError . new ( response ) , body [ :error_message ]
116- end
117-
118- if api_status == "INVALID_REQUEST"
136+ when 'INVALID_REQUEST'
119137 raise GoogleMapsService ::Error ::InvalidRequestError . new ( response ) , body [ :error_message ]
120- end
121-
122- if body [ :error_message ]
123- raise GoogleMapsService ::Error ::ApiError . new ( response ) , body [ :error_message ]
124138 else
125- raise GoogleMapsService ::Error ::ApiError . new ( response )
139+ raise GoogleMapsService ::Error ::ApiError . new ( response ) , body [ :error_message ]
126140 end
127141 end
128142
@@ -131,20 +145,20 @@ def check_response_status_code(response)
131145 when 200 ..300
132146 # Do-nothing
133147 when 301 , 302 , 303 , 307
134- message || = sprintf ( 'Redirect to %s' , response . header [ :location ] )
148+ message = sprintf ( 'Redirect to %s' , response . header [ :location ] )
135149 raise GoogleMapsService ::Error ::RedirectError . new ( response ) , message
136150 when 401
137- message || = 'Unauthorized'
138- raise GoogleMapsService ::Error ::ClientError . new ( response )
151+ message = 'Unauthorized'
152+ raise GoogleMapsService ::Error ::ClientError . new ( response ) , message
139153 when 304 , 400 , 402 ...500
140- message || = 'Invalid request'
141- raise GoogleMapsService ::Error ::ClientError . new ( response )
154+ message = 'Invalid request'
155+ raise GoogleMapsService ::Error ::ClientError . new ( response ) , message
142156 when 500 ..600
143- message || = 'Server error'
144- raise GoogleMapsService ::Error ::ServerError . new ( response )
157+ message = 'Server error'
158+ raise GoogleMapsService ::Error ::ServerError . new ( response ) , message
145159 else
146- message || = 'Unknown error'
147- raise GoogleMapsService ::Error ::TransmissionError . new ( response )
160+ message = 'Unknown error'
161+ raise GoogleMapsService ::Error ::Error . new ( response ) , message
148162 end
149163 end
150164
0 commit comments