| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "chrome/browser/chromeos/geolocation/simple_geolocation_request.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <string> | |
| 9 | |
| 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" | |
| 26 | |
| 27 // Location resolve timeout is usually 1 minute, so 2 minutes with 50 buckets | |
| 28 // should be enough. | |
| 29 #define UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES(name, sample) \ | |
| 30 UMA_HISTOGRAM_CUSTOM_TIMES(name, \ | |
| 31 sample, \ | |
| 32 base::TimeDelta::FromMilliseconds(10), \ | |
| 33 base::TimeDelta::FromMinutes(2), \ | |
| 34 50) | |
| 35 | |
| 36 namespace chromeos { | |
| 37 | |
| 38 namespace { | |
| 39 | |
| 40 // The full request text. (no parameters are supported by now) | |
| 41 const char kSimpleGeolocationRequestBody[] = "{\"considerIP\": \"true\"}"; | |
| 42 | |
| 43 // Response data. | |
| 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"; | |
| 53 | |
| 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; | |
| 57 | |
| 58 // Sleep between geolocation request retry on HTTP error. | |
| 59 const unsigned int kResolveGeolocationRetrySleepOnServerErrorSeconds = 5; | |
| 60 | |
| 61 // Sleep between geolocation request retry on bad server response. | |
| 62 const unsigned int kResolveGeolocationRetrySleepBadResponseSeconds = 10; | |
| 63 | |
| 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, | |
| 73 | |
| 74 // NOTE: Add entries only immediately above this line. | |
| 75 SIMPLE_GEOLOCATION_REQUEST_EVENT_COUNT = 5 | |
| 76 }; | |
| 77 | |
| 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, | |
| 86 | |
| 87 // NOTE: Add entries only immediately above this line. | |
| 88 SIMPLE_GEOLOCATION_REQUEST_RESULT_COUNT = 4 | |
| 89 }; | |
| 90 | |
| 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", | |
| 94 event, | |
| 95 SIMPLE_GEOLOCATION_REQUEST_EVENT_COUNT); | |
| 96 } | |
| 97 | |
| 98 void RecordUmaResponseCode(int code) { | |
| 99 UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleGeolocation.Request.ResponseCode", code); | |
| 100 } | |
| 101 | |
| 102 // Slow geolocation resolve leads to bad user experience. | |
| 103 void RecordUmaResponseTime(base::TimeDelta elapsed, bool success) { | |
| 104 if (success) { | |
| 105 UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES( | |
| 106 "SimpleGeolocation.Request.ResponseSuccessTime", elapsed); | |
| 107 } else { | |
| 108 UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES( | |
| 109 "SimpleGeolocation.Request.ResponseFailureTime", elapsed); | |
| 110 } | |
| 111 } | |
| 112 | |
| 113 void RecordUmaResult(SimpleGeolocationRequestResult result, size_t retries) { | |
| 114 UMA_HISTOGRAM_ENUMERATION("SimpleGeolocation.Request.Result", | |
| 115 result, | |
| 116 SIMPLE_GEOLOCATION_REQUEST_RESULT_COUNT); | |
| 117 UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleGeolocation.Request.Retries", | |
| 118 std::min(retries, kMaxRetriesValueInHistograms)); | |
| 119 } | |
| 120 | |
| 121 // Creates the request url to send to the server. | |
| 122 GURL GeolocationRequestURL(const GURL& url) { | |
| 123 if (url != SimpleGeolocationProvider::DefaultGeolocationProviderURL()) | |
| 124 return url; | |
| 125 | |
| 126 std::string api_key = google_apis::GetAPIKey(); | |
| 127 if (api_key.empty()) | |
| 128 return url; | |
| 129 | |
| 130 std::string query(url.query()); | |
| 131 if (!query.empty()) | |
| 132 query += "&"; | |
| 133 query += "key=" + net::EscapeQueryParamValue(api_key, true); | |
| 134 GURL::Replacements replacements; | |
| 135 replacements.SetQueryStr(query); | |
| 136 return url.ReplaceComponents(replacements); | |
| 137 } | |
| 138 | |
| 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(), | |
| 146 message.c_str()); | |
| 147 VLOG(1) << "SimpleGeolocationRequest::GetGeolocationFromResponse() : " | |
| 148 << position->error_message; | |
| 149 } | |
| 150 | |
| 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) { | |
| 157 DCHECK(position); | |
| 158 | |
| 159 if (response_body.empty()) { | |
| 160 PrintGeolocationError( | |
| 161 server_url, "Server returned empty response", position); | |
| 162 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_EMPTY); | |
| 163 return false; | |
| 164 } | |
| 165 VLOG(1) << "SimpleGeolocationRequest::ParseServerResponse() : " | |
| 166 "Parsing response '" << response_body << "'"; | |
| 167 | |
| 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); | |
| 176 return false; | |
| 177 } | |
| 178 | |
| 179 base::DictionaryValue* response_object = NULL; | |
| 180 if (!response_value->GetAsDictionary(&response_object)) { | |
| 181 PrintGeolocationError( | |
| 182 server_url, | |
| 183 "Unexpected response type : " + | |
| 184 base::StringPrintf("%u", response_value->GetType()), | |
| 185 position); | |
| 186 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED); | |
| 187 return false; | |
| 188 } | |
| 189 | |
| 190 base::DictionaryValue* error_object = NULL; | |
| 191 base::DictionaryValue* location_object = NULL; | |
| 192 response_object->GetDictionaryWithoutPathExpansion(kLocationString, | |
| 193 &location_object); | |
| 194 response_object->GetDictionaryWithoutPathExpansion(kErrorString, | |
| 195 &error_object); | |
| 196 | |
| 197 position->timestamp = base::Time::Now(); | |
| 198 | |
| 199 if (error_object) { | |
| 200 if (!error_object->GetStringWithoutPathExpansion( | |
| 201 kMessageString, &(position->error_message))) { | |
| 202 position->error_message = "Server returned error without message."; | |
| 203 } | |
| 204 | |
| 205 // Ignore result (code defaults to zero). | |
| 206 error_object->GetIntegerWithoutPathExpansion(kCodeString, | |
| 207 &(position->error_code)); | |
| 208 } else { | |
| 209 position->error_message.erase(); | |
| 210 } | |
| 211 | |
| 212 if (location_object) { | |
| 213 if (!location_object->GetDoubleWithoutPathExpansion( | |
| 214 kLatString, &(position->latitude))) { | |
| 215 PrintGeolocationError(server_url, "Missing 'lat' attribute.", position); | |
| 216 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED); | |
| 217 return false; | |
| 218 } | |
| 219 if (!location_object->GetDoubleWithoutPathExpansion( | |
| 220 kLngString, &(position->longitude))) { | |
| 221 PrintGeolocationError(server_url, "Missing 'lon' attribute.", position); | |
| 222 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED); | |
| 223 return false; | |
| 224 } | |
| 225 if (!response_object->GetDoubleWithoutPathExpansion( | |
| 226 kAccuracyString, &(position->accuracy))) { | |
| 227 PrintGeolocationError( | |
| 228 server_url, "Missing 'accuracy' attribute.", position); | |
| 229 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED); | |
| 230 return false; | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 if (error_object) { | |
| 235 position->status = Geoposition::STATUS_SERVER_ERROR; | |
| 236 return false; | |
| 237 } | |
| 238 // Empty response is STATUS_OK but not Valid(). | |
| 239 position->status = Geoposition::STATUS_OK; | |
| 240 return true; | |
| 241 } | |
| 242 | |
| 243 // Attempts to extract a position from the response. Detects and indicates | |
| 244 // various failure cases. | |
| 245 bool GetGeolocationFromResponse(bool http_success, | |
| 246 int status_code, | |
| 247 const std::string& response_body, | |
| 248 const GURL& server_url, | |
| 249 Geoposition* position) { | |
| 250 | |
| 251 // HttpPost can fail for a number of reasons. Most likely this is because | |
| 252 // we're offline, or there was no response. | |
| 253 if (!http_success) { | |
| 254 PrintGeolocationError(server_url, "No response received", position); | |
| 255 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_EMPTY); | |
| 256 return false; | |
| 257 } | |
| 258 if (status_code != net::HTTP_OK) { | |
| 259 std::string message = "Returned error code "; | |
| 260 message += base::IntToString(status_code); | |
| 261 PrintGeolocationError(server_url, message, position); | |
| 262 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_NOT_OK); | |
| 263 return false; | |
| 264 } | |
| 265 | |
| 266 return ParseServerResponse(server_url, response_body, position); | |
| 267 } | |
| 268 | |
| 269 } // namespace | |
| 270 | |
| 271 SimpleGeolocationRequest::SimpleGeolocationRequest( | |
| 272 net::URLRequestContextGetter* url_context_getter, | |
| 273 const GURL& service_url, | |
| 274 base::TimeDelta timeout) | |
| 275 : url_context_getter_(url_context_getter), | |
| 276 service_url_(service_url), | |
| 277 retry_sleep_on_server_error_(base::TimeDelta::FromSeconds( | |
| 278 kResolveGeolocationRetrySleepOnServerErrorSeconds)), | |
| 279 retry_sleep_on_bad_response_(base::TimeDelta::FromSeconds( | |
| 280 kResolveGeolocationRetrySleepBadResponseSeconds)), | |
| 281 timeout_(timeout), | |
| 282 retries_(0) { | |
| 283 } | |
| 284 | |
| 285 SimpleGeolocationRequest::~SimpleGeolocationRequest() { | |
| 286 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 287 | |
| 288 // If callback is not empty, request is cancelled. | |
| 289 if (!callback_.is_null()) { | |
| 290 RecordUmaResponseTime(base::Time::Now() - request_started_at_, false); | |
| 291 RecordUmaResult(SIMPLE_GEOLOCATION_REQUEST_RESULT_CANCELLED, retries_); | |
| 292 } | |
| 293 } | |
| 294 | |
| 295 void SimpleGeolocationRequest::StartRequest() { | |
| 296 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 297 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_REQUEST_START); | |
| 298 ++retries_; | |
| 299 | |
| 300 url_fetcher_.reset( | |
| 301 net::URLFetcher::Create(request_url_, net::URLFetcher::POST, this)); | |
| 302 url_fetcher_->SetRequestContext(url_context_getter_.get()); | |
| 303 url_fetcher_->SetUploadData("application/json", | |
| 304 std::string(kSimpleGeolocationRequestBody)); | |
| 305 url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | | |
| 306 net::LOAD_DISABLE_CACHE | | |
| 307 net::LOAD_DO_NOT_SAVE_COOKIES | | |
| 308 net::LOAD_DO_NOT_SEND_COOKIES | | |
| 309 net::LOAD_DO_NOT_SEND_AUTH_DATA); | |
| 310 url_fetcher_->Start(); | |
| 311 } | |
| 312 | |
| 313 void SimpleGeolocationRequest::MakeRequest(const ResponseCallback& callback) { | |
| 314 callback_ = callback; | |
| 315 request_url_ = GeolocationRequestURL(service_url_); | |
| 316 timeout_timer_.Start( | |
| 317 FROM_HERE, timeout_, this, &SimpleGeolocationRequest::OnTimeout); | |
| 318 request_started_at_ = base::Time::Now(); | |
| 319 StartRequest(); | |
| 320 } | |
| 321 | |
| 322 void SimpleGeolocationRequest::Retry(bool server_error) { | |
| 323 base::TimeDelta delay(server_error ? retry_sleep_on_server_error_ | |
| 324 : retry_sleep_on_bad_response_); | |
| 325 request_scheduled_.Start( | |
| 326 FROM_HERE, delay, this, &SimpleGeolocationRequest::StartRequest); | |
| 327 } | |
| 328 | |
| 329 void SimpleGeolocationRequest::OnURLFetchComplete( | |
| 330 const net::URLFetcher* source) { | |
| 331 DCHECK_EQ(url_fetcher_.get(), source); | |
| 332 | |
| 333 net::URLRequestStatus status = source->GetStatus(); | |
| 334 int response_code = source->GetResponseCode(); | |
| 335 RecordUmaResponseCode(response_code); | |
| 336 | |
| 337 std::string data; | |
| 338 source->GetResponseAsString(&data); | |
| 339 const bool parse_success = GetGeolocationFromResponse( | |
| 340 status.is_success(), response_code, data, source->GetURL(), &position_); | |
| 341 const bool server_error = | |
| 342 !status.is_success() || (response_code >= 500 && response_code < 600); | |
| 343 const bool success = parse_success && position_.Valid(); | |
| 344 url_fetcher_.reset(); | |
| 345 | |
| 346 DVLOG(1) << "SimpleGeolocationRequest::OnURLFetchComplete(): position={" | |
| 347 << position_.ToString() << "}"; | |
| 348 | |
| 349 if (!success) { | |
| 350 Retry(server_error); | |
| 351 return; | |
| 352 } | |
| 353 const base::TimeDelta elapsed = base::Time::Now() - request_started_at_; | |
| 354 RecordUmaResponseTime(elapsed, success); | |
| 355 | |
| 356 RecordUmaResult(SIMPLE_GEOLOCATION_REQUEST_RESULT_SUCCESS, retries_); | |
| 357 | |
| 358 ReplyAndDestroySelf(elapsed, server_error); | |
| 359 // "this" is already destroyed here. | |
| 360 } | |
| 361 | |
| 362 void SimpleGeolocationRequest::ReplyAndDestroySelf( | |
| 363 const base::TimeDelta elapsed, | |
| 364 bool server_error) { | |
| 365 url_fetcher_.reset(); | |
| 366 timeout_timer_.Stop(); | |
| 367 request_scheduled_.Stop(); | |
| 368 | |
| 369 ResponseCallback callback = callback_; | |
| 370 | |
| 371 // Empty callback is used to identify "completed or not yet started request". | |
| 372 callback_.Reset(); | |
| 373 | |
| 374 // callback.Run() usually destroys SimpleGeolocationRequest, because this is | |
| 375 // the way callback is implemented in GeolocationProvider. | |
| 376 callback.Run(position_, server_error, elapsed); | |
| 377 // "this" is already destroyed here. | |
| 378 } | |
| 379 | |
| 380 void SimpleGeolocationRequest::OnTimeout() { | |
| 381 const SimpleGeolocationRequestResult result = | |
| 382 (position_.status == Geoposition::STATUS_SERVER_ERROR | |
| 383 ? SIMPLE_GEOLOCATION_REQUEST_RESULT_SERVER_ERROR | |
| 384 : SIMPLE_GEOLOCATION_REQUEST_RESULT_FAILURE); | |
| 385 RecordUmaResult(result, retries_); | |
| 386 position_.status = Geoposition::STATUS_TIMEOUT; | |
| 387 const base::TimeDelta elapsed = base::Time::Now() - request_started_at_; | |
| 388 ReplyAndDestroySelf(elapsed, true /* server_error */); | |
| 389 // "this" is already destroyed here. | |
| 390 } | |
| 391 | |
| 392 } // namespace chromeos | |
| OLD | NEW |