1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/geolocation/simple_geolocation_request.h"
10 #include "base/json/json_reader.h"
11 #include "base/metrics/histogram.h"
12 #include "base/metrics/sparse_histogram.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/time/time.h"
16 #include "base/values.h"
17 #include "chrome/browser/chromeos/geolocation/geoposition.h"
18 #include "chrome/browser/chromeos/geolocation/simple_geolocation_provider.h"
19 #include "google_apis/google_api_keys.h"
20 #include "net/base/escape.h"
21 #include "net/base/load_flags.h"
22 #include "net/http/http_status_code.h"
23 #include "net/url_request/url_fetcher.h"
24 #include "net/url_request/url_request_context_getter.h"
25 #include "net/url_request/url_request_status.h"
27 // Location resolve timeout is usually 1 minute, so 2 minutes with 50 buckets
29 #define UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES(name, sample) \
30 UMA_HISTOGRAM_CUSTOM_TIMES(name, \
32 base::TimeDelta::FromMilliseconds(10), \
33 base::TimeDelta::FromMinutes(2), \
40 // The full request text. (no parameters are supported by now)
41 const char kSimpleGeolocationRequestBody
[] = "{\"considerIP\": \"true\"}";
44 const char kLocationString
[] = "location";
45 const char kLatString
[] = "lat";
46 const char kLngString
[] = "lng";
47 const char kAccuracyString
[] = "accuracy";
48 // Error object and its contents.
49 const char kErrorString
[] = "error";
50 // "errors" array in "erorr" object is ignored.
51 const char kCodeString
[] = "code";
52 const char kMessageString
[] = "message";
54 // We are using "sparse" histograms for the number of retry attempts,
55 // so we need to explicitly limit maximum value (in case something goes wrong).
56 const size_t kMaxRetriesValueInHistograms
= 20;
58 // Sleep between geolocation request retry on HTTP error.
59 const unsigned int kResolveGeolocationRetrySleepOnServerErrorSeconds
= 5;
61 // Sleep between geolocation request retry on bad server response.
62 const unsigned int kResolveGeolocationRetrySleepBadResponseSeconds
= 10;
64 enum SimpleGeolocationRequestEvent
{
65 // NOTE: Do not renumber these as that would confuse interpretation of
66 // previously logged data. When making changes, also update the enum list
67 // in tools/metrics/histograms/histograms.xml to keep it in sync.
68 SIMPLE_GEOLOCATION_REQUEST_EVENT_REQUEST_START
= 0,
69 SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_SUCCESS
= 1,
70 SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_NOT_OK
= 2,
71 SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_EMPTY
= 3,
72 SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED
= 4,
74 // NOTE: Add entries only immediately above this line.
75 SIMPLE_GEOLOCATION_REQUEST_EVENT_COUNT
= 5
78 enum SimpleGeolocationRequestResult
{
79 // NOTE: Do not renumber these as that would confuse interpretation of
80 // previously logged data. When making changes, also update the enum list
81 // in tools/metrics/histograms/histograms.xml to keep it in sync.
82 SIMPLE_GEOLOCATION_REQUEST_RESULT_SUCCESS
= 0,
83 SIMPLE_GEOLOCATION_REQUEST_RESULT_FAILURE
= 1,
84 SIMPLE_GEOLOCATION_REQUEST_RESULT_SERVER_ERROR
= 2,
85 SIMPLE_GEOLOCATION_REQUEST_RESULT_CANCELLED
= 3,
87 // NOTE: Add entries only immediately above this line.
88 SIMPLE_GEOLOCATION_REQUEST_RESULT_COUNT
= 4
91 // Too many requests (more than 1) mean there is a problem in implementation.
92 void RecordUmaEvent(SimpleGeolocationRequestEvent event
) {
93 UMA_HISTOGRAM_ENUMERATION("SimpleGeolocation.Request.Event",
95 SIMPLE_GEOLOCATION_REQUEST_EVENT_COUNT
);
98 void RecordUmaResponseCode(int code
) {
99 UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleGeolocation.Request.ResponseCode", code
);
102 // Slow geolocation resolve leads to bad user experience.
103 void RecordUmaResponseTime(base::TimeDelta elapsed
, bool success
) {
105 UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES(
106 "SimpleGeolocation.Request.ResponseSuccessTime", elapsed
);
108 UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES(
109 "SimpleGeolocation.Request.ResponseFailureTime", elapsed
);
113 void RecordUmaResult(SimpleGeolocationRequestResult result
, size_t retries
) {
114 UMA_HISTOGRAM_ENUMERATION("SimpleGeolocation.Request.Result",
116 SIMPLE_GEOLOCATION_REQUEST_RESULT_COUNT
);
117 UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleGeolocation.Request.Retries",
118 std::min(retries
, kMaxRetriesValueInHistograms
));
121 // Creates the request url to send to the server.
122 GURL
GeolocationRequestURL(const GURL
& url
) {
123 if (url
!= SimpleGeolocationProvider::DefaultGeolocationProviderURL())
126 std::string api_key
= google_apis::GetAPIKey();
130 std::string
query(url
.query());
133 query
+= "key=" + net::EscapeQueryParamValue(api_key
, true);
134 GURL::Replacements replacements
;
135 replacements
.SetQueryStr(query
);
136 return url
.ReplaceComponents(replacements
);
139 void PrintGeolocationError(const GURL
& server_url
,
140 const std::string
& message
,
141 Geoposition
* position
) {
142 position
->status
= Geoposition::STATUS_SERVER_ERROR
;
143 position
->error_message
=
144 base::StringPrintf("SimpleGeolocation provider at '%s' : %s.",
145 server_url
.GetOrigin().spec().c_str(),
147 VLOG(1) << "SimpleGeolocationRequest::GetGeolocationFromResponse() : "
148 << position
->error_message
;
151 // Parses the server response body. Returns true if parsing was successful.
152 // Sets |*position| to the parsed Geolocation if a valid position was received,
153 // otherwise leaves it unchanged.
154 bool ParseServerResponse(const GURL
& server_url
,
155 const std::string
& response_body
,
156 Geoposition
* position
) {
159 if (response_body
.empty()) {
160 PrintGeolocationError(
161 server_url
, "Server returned empty response", position
);
162 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_EMPTY
);
165 VLOG(1) << "SimpleGeolocationRequest::ParseServerResponse() : "
166 "Parsing response '" << response_body
<< "'";
168 // Parse the response, ignoring comments.
169 std::string error_msg
;
170 scoped_ptr
<base::Value
> response_value(base::JSONReader::ReadAndReturnError(
171 response_body
, base::JSON_PARSE_RFC
, NULL
, &error_msg
));
172 if (response_value
== NULL
) {
173 PrintGeolocationError(
174 server_url
, "JSONReader failed: " + error_msg
, position
);
175 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED
);
179 base::DictionaryValue
* response_object
= NULL
;
180 if (!response_value
->GetAsDictionary(&response_object
)) {
181 PrintGeolocationError(
183 "Unexpected response type : " +
184 base::StringPrintf("%u", response_value
->GetType()),
186 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED
);
190 base::DictionaryValue
* error_object
= NULL
;
191 base::DictionaryValue
* location_object
= NULL
;
192 response_object
->GetDictionaryWithoutPathExpansion(kLocationString
,
194 response_object
->GetDictionaryWithoutPathExpansion(kErrorString
,
197 position
->timestamp
= base::Time::Now();
200 if (!error_object
->GetStringWithoutPathExpansion(
201 kMessageString
, &(position
->error_message
))) {
202 position
->error_message
= "Server returned error without message.";
205 // Ignore result (code defaults to zero).
206 error_object
->GetIntegerWithoutPathExpansion(kCodeString
,
207 &(position
->error_code
));
210 if (location_object
) {
211 if (!location_object
->GetDoubleWithoutPathExpansion(
212 kLatString
, &(position
->latitude
))) {
213 PrintGeolocationError(server_url
, "Missing 'lat' attribute.", position
);
214 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED
);
217 if (!location_object
->GetDoubleWithoutPathExpansion(
218 kLngString
, &(position
->longitude
))) {
219 PrintGeolocationError(server_url
, "Missing 'lon' attribute.", position
);
220 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED
);
223 if (!response_object
->GetDoubleWithoutPathExpansion(
224 kAccuracyString
, &(position
->accuracy
))) {
225 PrintGeolocationError(
226 server_url
, "Missing 'accuracy' attribute.", position
);
227 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED
);
233 position
->status
= Geoposition::STATUS_SERVER_ERROR
;
236 // Empty response is STATUS_OK but not Valid().
237 position
->status
= Geoposition::STATUS_OK
;
241 // Attempts to extract a position from the response. Detects and indicates
242 // various failure cases.
243 bool GetGeolocationFromResponse(bool http_success
,
245 const std::string
& response_body
,
246 const GURL
& server_url
,
247 Geoposition
* position
) {
249 // HttpPost can fail for a number of reasons. Most likely this is because
250 // we're offline, or there was no response.
252 PrintGeolocationError(server_url
, "No response received", position
);
253 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_EMPTY
);
256 if (status_code
!= net::HTTP_OK
) {
257 std::string message
= "Returned error code ";
258 message
+= base::IntToString(status_code
);
259 PrintGeolocationError(server_url
, message
, position
);
260 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_NOT_OK
);
264 return ParseServerResponse(server_url
, response_body
, position
);
269 SimpleGeolocationRequest::SimpleGeolocationRequest(
270 net::URLRequestContextGetter
* url_context_getter
,
271 const GURL
& service_url
,
272 base::TimeDelta timeout
)
273 : url_context_getter_(url_context_getter
),
274 service_url_(service_url
),
279 SimpleGeolocationRequest::~SimpleGeolocationRequest() {
280 DCHECK(thread_checker_
.CalledOnValidThread());
282 // If callback is not empty, request is cancelled.
283 if (!callback_
.is_null()) {
284 RecordUmaResponseTime(base::Time::Now() - request_started_at_
, false);
285 RecordUmaResult(SIMPLE_GEOLOCATION_REQUEST_RESULT_CANCELLED
, retries_
);
289 void SimpleGeolocationRequest::StartRequest() {
290 DCHECK(thread_checker_
.CalledOnValidThread());
291 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_REQUEST_START
);
295 net::URLFetcher::Create(request_url_
, net::URLFetcher::POST
, this));
296 url_fetcher_
->SetRequestContext(url_context_getter_
);
297 url_fetcher_
->SetUploadData("application/json",
298 std::string(kSimpleGeolocationRequestBody
));
299 url_fetcher_
->SetLoadFlags(net::LOAD_BYPASS_CACHE
|
300 net::LOAD_DISABLE_CACHE
|
301 net::LOAD_DO_NOT_SAVE_COOKIES
|
302 net::LOAD_DO_NOT_SEND_COOKIES
|
303 net::LOAD_DO_NOT_SEND_AUTH_DATA
);
304 url_fetcher_
->Start();
307 void SimpleGeolocationRequest::MakeRequest(const ResponseCallback
& callback
) {
308 callback_
= callback
;
309 request_url_
= GeolocationRequestURL(service_url_
);
310 timeout_timer_
.Start(
311 FROM_HERE
, timeout_
, this, &SimpleGeolocationRequest::OnTimeout
);
312 request_started_at_
= base::Time::Now();
316 void SimpleGeolocationRequest::Retry(bool server_error
) {
317 const base::TimeDelta delay
= base::TimeDelta::FromSeconds(
318 server_error
? kResolveGeolocationRetrySleepOnServerErrorSeconds
319 : kResolveGeolocationRetrySleepBadResponseSeconds
);
320 request_scheduled_
.Start(
321 FROM_HERE
, delay
, this, &SimpleGeolocationRequest::StartRequest
);
324 void SimpleGeolocationRequest::OnURLFetchComplete(
325 const net::URLFetcher
* source
) {
326 DCHECK_EQ(url_fetcher_
.get(), source
);
328 net::URLRequestStatus status
= source
->GetStatus();
329 int response_code
= source
->GetResponseCode();
330 RecordUmaResponseCode(response_code
);
333 source
->GetResponseAsString(&data
);
334 const bool parse_success
= GetGeolocationFromResponse(
335 status
.is_success(), response_code
, data
, source
->GetURL(), &position_
);
336 const bool server_error
=
337 !status
.is_success() || (response_code
>= 500 && response_code
< 600);
338 const bool success
= parse_success
&& position_
.Valid();
339 url_fetcher_
.reset();
341 DVLOG(1) << "SimpleGeolocationRequest::OnURLFetchComplete(): position={"
342 << position_
.ToString() << "}";
348 const base::TimeDelta elapsed
= base::Time::Now() - request_started_at_
;
349 RecordUmaResponseTime(elapsed
, success
);
351 RecordUmaResult(SIMPLE_GEOLOCATION_REQUEST_RESULT_SUCCESS
, retries_
);
353 ReplyAndDestroySelf(elapsed
, server_error
);
354 // "this" is already destroyed here.
357 void SimpleGeolocationRequest::ReplyAndDestroySelf(
358 const base::TimeDelta elapsed
,
360 url_fetcher_
.reset();
361 timeout_timer_
.Stop();
362 request_scheduled_
.Stop();
364 ResponseCallback callback
= callback_
;
366 // Empty callback is used to identify "completed or not yet started request".
369 // callback.Run() usually destroys SimpleGeolocationRequest, because this is
370 // the way callback is implemented in GeolocationProvider.
371 callback
.Run(position_
, server_error
, elapsed
);
372 // "this" is already destroyed here.
375 void SimpleGeolocationRequest::OnTimeout() {
376 const SimpleGeolocationRequestResult result
=
377 (position_
.status
== Geoposition::STATUS_SERVER_ERROR
378 ? SIMPLE_GEOLOCATION_REQUEST_RESULT_SERVER_ERROR
379 : SIMPLE_GEOLOCATION_REQUEST_RESULT_FAILURE
);
380 RecordUmaResult(result
, retries_
);
381 position_
.status
= Geoposition::STATUS_TIMEOUT
;
382 const base::TimeDelta elapsed
= base::Time::Now() - request_started_at_
;
383 ReplyAndDestroySelf(elapsed
, true /* server_error */);
384 // "this" is already destroyed here.
387 } // namespace chromeos