Don't preload rarely seen large images
[chromium-blink-merge.git] / chromeos / geolocation / simple_geolocation_request.cc
blob90cfad3f893a27d9ddcdaeb0d0a5c34c2c0a45a9
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 "chromeos/geolocation/simple_geolocation_request.h"
7 #include <algorithm>
8 #include <string>
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 "chromeos/geolocation/simple_geolocation_provider.h"
18 #include "google_apis/google_api_keys.h"
19 #include "net/base/escape.h"
20 #include "net/base/load_flags.h"
21 #include "net/http/http_status_code.h"
22 #include "net/url_request/url_request_context_getter.h"
23 #include "net/url_request/url_request_status.h"
25 // Location resolve timeout is usually 1 minute, so 2 minutes with 50 buckets
26 // should be enough.
27 #define UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES(name, sample) \
28 UMA_HISTOGRAM_CUSTOM_TIMES(name, \
29 sample, \
30 base::TimeDelta::FromMilliseconds(10), \
31 base::TimeDelta::FromMinutes(2), \
32 50)
34 namespace chromeos {
36 namespace {
38 // The full request text. (no parameters are supported by now)
39 const char kSimpleGeolocationRequestBody[] = "{\"considerIP\": \"true\"}";
41 // Response data.
42 const char kLocationString[] = "location";
43 const char kLatString[] = "lat";
44 const char kLngString[] = "lng";
45 const char kAccuracyString[] = "accuracy";
46 // Error object and its contents.
47 const char kErrorString[] = "error";
48 // "errors" array in "erorr" object is ignored.
49 const char kCodeString[] = "code";
50 const char kMessageString[] = "message";
52 // We are using "sparse" histograms for the number of retry attempts,
53 // so we need to explicitly limit maximum value (in case something goes wrong).
54 const size_t kMaxRetriesValueInHistograms = 20;
56 // Sleep between geolocation request retry on HTTP error.
57 const unsigned int kResolveGeolocationRetrySleepOnServerErrorSeconds = 5;
59 // Sleep between geolocation request retry on bad server response.
60 const unsigned int kResolveGeolocationRetrySleepBadResponseSeconds = 10;
62 enum SimpleGeolocationRequestEvent {
63 // NOTE: Do not renumber these as that would confuse interpretation of
64 // previously logged data. When making changes, also update the enum list
65 // in tools/metrics/histograms/histograms.xml to keep it in sync.
66 SIMPLE_GEOLOCATION_REQUEST_EVENT_REQUEST_START = 0,
67 SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_SUCCESS = 1,
68 SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_NOT_OK = 2,
69 SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_EMPTY = 3,
70 SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED = 4,
72 // NOTE: Add entries only immediately above this line.
73 SIMPLE_GEOLOCATION_REQUEST_EVENT_COUNT = 5
76 enum SimpleGeolocationRequestResult {
77 // NOTE: Do not renumber these as that would confuse interpretation of
78 // previously logged data. When making changes, also update the enum list
79 // in tools/metrics/histograms/histograms.xml to keep it in sync.
80 SIMPLE_GEOLOCATION_REQUEST_RESULT_SUCCESS = 0,
81 SIMPLE_GEOLOCATION_REQUEST_RESULT_FAILURE = 1,
82 SIMPLE_GEOLOCATION_REQUEST_RESULT_SERVER_ERROR = 2,
83 SIMPLE_GEOLOCATION_REQUEST_RESULT_CANCELLED = 3,
85 // NOTE: Add entries only immediately above this line.
86 SIMPLE_GEOLOCATION_REQUEST_RESULT_COUNT = 4
89 // Too many requests (more than 1) mean there is a problem in implementation.
90 void RecordUmaEvent(SimpleGeolocationRequestEvent event) {
91 UMA_HISTOGRAM_ENUMERATION("SimpleGeolocation.Request.Event",
92 event,
93 SIMPLE_GEOLOCATION_REQUEST_EVENT_COUNT);
96 void RecordUmaResponseCode(int code) {
97 UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleGeolocation.Request.ResponseCode", code);
100 // Slow geolocation resolve leads to bad user experience.
101 void RecordUmaResponseTime(base::TimeDelta elapsed, bool success) {
102 if (success) {
103 UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES(
104 "SimpleGeolocation.Request.ResponseSuccessTime", elapsed);
105 } else {
106 UMA_HISTOGRAM_LOCATION_RESPONSE_TIMES(
107 "SimpleGeolocation.Request.ResponseFailureTime", elapsed);
111 void RecordUmaResult(SimpleGeolocationRequestResult result, size_t retries) {
112 UMA_HISTOGRAM_ENUMERATION("SimpleGeolocation.Request.Result",
113 result,
114 SIMPLE_GEOLOCATION_REQUEST_RESULT_COUNT);
115 UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleGeolocation.Request.Retries",
116 std::min(retries, kMaxRetriesValueInHistograms));
119 // Creates the request url to send to the server.
120 GURL GeolocationRequestURL(const GURL& url) {
121 if (url != SimpleGeolocationProvider::DefaultGeolocationProviderURL())
122 return url;
124 std::string api_key = google_apis::GetAPIKey();
125 if (api_key.empty())
126 return url;
128 std::string query(url.query());
129 if (!query.empty())
130 query += "&";
131 query += "key=" + net::EscapeQueryParamValue(api_key, true);
132 GURL::Replacements replacements;
133 replacements.SetQueryStr(query);
134 return url.ReplaceComponents(replacements);
137 void PrintGeolocationError(const GURL& server_url,
138 const std::string& message,
139 Geoposition* position) {
140 position->status = Geoposition::STATUS_SERVER_ERROR;
141 position->error_message =
142 base::StringPrintf("SimpleGeolocation provider at '%s' : %s.",
143 server_url.GetOrigin().spec().c_str(),
144 message.c_str());
145 VLOG(1) << "SimpleGeolocationRequest::GetGeolocationFromResponse() : "
146 << position->error_message;
149 // Parses the server response body. Returns true if parsing was successful.
150 // Sets |*position| to the parsed Geolocation if a valid position was received,
151 // otherwise leaves it unchanged.
152 bool ParseServerResponse(const GURL& server_url,
153 const std::string& response_body,
154 Geoposition* position) {
155 DCHECK(position);
157 if (response_body.empty()) {
158 PrintGeolocationError(
159 server_url, "Server returned empty response", position);
160 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_EMPTY);
161 return false;
163 VLOG(1) << "SimpleGeolocationRequest::ParseServerResponse() : "
164 "Parsing response '" << response_body << "'";
166 // Parse the response, ignoring comments.
167 std::string error_msg;
168 scoped_ptr<base::Value> response_value(
169 base::JSONReader::DeprecatedReadAndReturnError(
170 response_body, base::JSON_PARSE_RFC, NULL, &error_msg));
171 if (response_value == NULL) {
172 PrintGeolocationError(
173 server_url, "JSONReader failed: " + error_msg, position);
174 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED);
175 return false;
178 base::DictionaryValue* response_object = NULL;
179 if (!response_value->GetAsDictionary(&response_object)) {
180 PrintGeolocationError(
181 server_url,
182 "Unexpected response type : " +
183 base::StringPrintf("%u", response_value->GetType()),
184 position);
185 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED);
186 return false;
189 base::DictionaryValue* error_object = NULL;
190 base::DictionaryValue* location_object = NULL;
191 response_object->GetDictionaryWithoutPathExpansion(kLocationString,
192 &location_object);
193 response_object->GetDictionaryWithoutPathExpansion(kErrorString,
194 &error_object);
196 position->timestamp = base::Time::Now();
198 if (error_object) {
199 if (!error_object->GetStringWithoutPathExpansion(
200 kMessageString, &(position->error_message))) {
201 position->error_message = "Server returned error without message.";
204 // Ignore result (code defaults to zero).
205 error_object->GetIntegerWithoutPathExpansion(kCodeString,
206 &(position->error_code));
207 } else {
208 position->error_message.erase();
211 if (location_object) {
212 if (!location_object->GetDoubleWithoutPathExpansion(
213 kLatString, &(position->latitude))) {
214 PrintGeolocationError(server_url, "Missing 'lat' attribute.", position);
215 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED);
216 return false;
218 if (!location_object->GetDoubleWithoutPathExpansion(
219 kLngString, &(position->longitude))) {
220 PrintGeolocationError(server_url, "Missing 'lon' attribute.", position);
221 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED);
222 return false;
224 if (!response_object->GetDoubleWithoutPathExpansion(
225 kAccuracyString, &(position->accuracy))) {
226 PrintGeolocationError(
227 server_url, "Missing 'accuracy' attribute.", position);
228 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_MALFORMED);
229 return false;
233 if (error_object) {
234 position->status = Geoposition::STATUS_SERVER_ERROR;
235 return false;
237 // Empty response is STATUS_OK but not Valid().
238 position->status = Geoposition::STATUS_OK;
239 return true;
242 // Attempts to extract a position from the response. Detects and indicates
243 // various failure cases.
244 bool GetGeolocationFromResponse(bool http_success,
245 int status_code,
246 const std::string& response_body,
247 const GURL& server_url,
248 Geoposition* position) {
250 // HttpPost can fail for a number of reasons. Most likely this is because
251 // we're offline, or there was no response.
252 if (!http_success) {
253 PrintGeolocationError(server_url, "No response received", position);
254 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_EMPTY);
255 return false;
257 if (status_code != net::HTTP_OK) {
258 std::string message = "Returned error code ";
259 message += base::IntToString(status_code);
260 PrintGeolocationError(server_url, message, position);
261 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_RESPONSE_NOT_OK);
262 return false;
265 return ParseServerResponse(server_url, response_body, position);
268 } // namespace
270 SimpleGeolocationRequest::SimpleGeolocationRequest(
271 net::URLRequestContextGetter* url_context_getter,
272 const GURL& service_url,
273 base::TimeDelta timeout)
274 : url_context_getter_(url_context_getter),
275 service_url_(service_url),
276 retry_sleep_on_server_error_(base::TimeDelta::FromSeconds(
277 kResolveGeolocationRetrySleepOnServerErrorSeconds)),
278 retry_sleep_on_bad_response_(base::TimeDelta::FromSeconds(
279 kResolveGeolocationRetrySleepBadResponseSeconds)),
280 timeout_(timeout),
281 retries_(0) {
284 SimpleGeolocationRequest::~SimpleGeolocationRequest() {
285 DCHECK(thread_checker_.CalledOnValidThread());
287 // If callback is not empty, request is cancelled.
288 if (!callback_.is_null()) {
289 RecordUmaResponseTime(base::Time::Now() - request_started_at_, false);
290 RecordUmaResult(SIMPLE_GEOLOCATION_REQUEST_RESULT_CANCELLED, retries_);
294 void SimpleGeolocationRequest::StartRequest() {
295 DCHECK(thread_checker_.CalledOnValidThread());
296 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_REQUEST_START);
297 ++retries_;
299 url_fetcher_ =
300 net::URLFetcher::Create(request_url_, net::URLFetcher::POST, this);
301 url_fetcher_->SetRequestContext(url_context_getter_.get());
302 url_fetcher_->SetUploadData("application/json",
303 std::string(kSimpleGeolocationRequestBody));
304 url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
305 net::LOAD_DISABLE_CACHE |
306 net::LOAD_DO_NOT_SAVE_COOKIES |
307 net::LOAD_DO_NOT_SEND_COOKIES |
308 net::LOAD_DO_NOT_SEND_AUTH_DATA);
309 url_fetcher_->Start();
312 void SimpleGeolocationRequest::MakeRequest(const ResponseCallback& callback) {
313 callback_ = callback;
314 request_url_ = GeolocationRequestURL(service_url_);
315 timeout_timer_.Start(
316 FROM_HERE, timeout_, this, &SimpleGeolocationRequest::OnTimeout);
317 request_started_at_ = base::Time::Now();
318 StartRequest();
321 void SimpleGeolocationRequest::Retry(bool server_error) {
322 base::TimeDelta delay(server_error ? retry_sleep_on_server_error_
323 : retry_sleep_on_bad_response_);
324 request_scheduled_.Start(
325 FROM_HERE, delay, this, &SimpleGeolocationRequest::StartRequest);
328 void SimpleGeolocationRequest::OnURLFetchComplete(
329 const net::URLFetcher* source) {
330 DCHECK_EQ(url_fetcher_.get(), source);
332 net::URLRequestStatus status = source->GetStatus();
333 int response_code = source->GetResponseCode();
334 RecordUmaResponseCode(response_code);
336 std::string data;
337 source->GetResponseAsString(&data);
338 const bool parse_success = GetGeolocationFromResponse(
339 status.is_success(), response_code, data, source->GetURL(), &position_);
340 const bool server_error =
341 !status.is_success() || (response_code >= 500 && response_code < 600);
342 const bool success = parse_success && position_.Valid();
343 url_fetcher_.reset();
345 DVLOG(1) << "SimpleGeolocationRequest::OnURLFetchComplete(): position={"
346 << position_.ToString() << "}";
348 if (!success) {
349 Retry(server_error);
350 return;
352 const base::TimeDelta elapsed = base::Time::Now() - request_started_at_;
353 RecordUmaResponseTime(elapsed, success);
355 RecordUmaResult(SIMPLE_GEOLOCATION_REQUEST_RESULT_SUCCESS, retries_);
357 ReplyAndDestroySelf(elapsed, server_error);
358 // "this" is already destroyed here.
361 void SimpleGeolocationRequest::ReplyAndDestroySelf(
362 const base::TimeDelta elapsed,
363 bool server_error) {
364 url_fetcher_.reset();
365 timeout_timer_.Stop();
366 request_scheduled_.Stop();
368 ResponseCallback callback = callback_;
370 // Empty callback is used to identify "completed or not yet started request".
371 callback_.Reset();
373 // callback.Run() usually destroys SimpleGeolocationRequest, because this is
374 // the way callback is implemented in GeolocationProvider.
375 callback.Run(position_, server_error, elapsed);
376 // "this" is already destroyed here.
379 void SimpleGeolocationRequest::OnTimeout() {
380 const SimpleGeolocationRequestResult result =
381 (position_.status == Geoposition::STATUS_SERVER_ERROR
382 ? SIMPLE_GEOLOCATION_REQUEST_RESULT_SERVER_ERROR
383 : SIMPLE_GEOLOCATION_REQUEST_RESULT_FAILURE);
384 RecordUmaResult(result, retries_);
385 position_.status = Geoposition::STATUS_TIMEOUT;
386 const base::TimeDelta elapsed = base::Time::Now() - request_started_at_;
387 ReplyAndDestroySelf(elapsed, true /* server_error */);
388 // "this" is already destroyed here.
391 } // namespace chromeos