[ServiceWorker] Implement WebServiceWorkerContextClient::openWindow().
[chromium-blink-merge.git] / chromeos / geolocation / simple_geolocation_request.cc
blobc2cb8c3f32540d4ca0b5c1e4a5d051d25f60e479
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/geoposition.h"
18 #include "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
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)
36 namespace chromeos {
38 namespace {
40 // The full request text. (no parameters are supported by now)
41 const char kSimpleGeolocationRequestBody[] = "{\"considerIP\": \"true\"}";
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";
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",
94 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) {
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);
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));
121 // Creates the request url to send to the server.
122 GURL GeolocationRequestURL(const GURL& url) {
123 if (url != SimpleGeolocationProvider::DefaultGeolocationProviderURL())
124 return url;
126 std::string api_key = google_apis::GetAPIKey();
127 if (api_key.empty())
128 return url;
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);
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;
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);
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;
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);
176 return false;
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;
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);
197 position->timestamp = base::Time::Now();
199 if (error_object) {
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));
208 } else {
209 position->error_message.erase();
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;
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;
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;
234 if (error_object) {
235 position->status = Geoposition::STATUS_SERVER_ERROR;
236 return false;
238 // Empty response is STATUS_OK but not Valid().
239 position->status = Geoposition::STATUS_OK;
240 return true;
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) {
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;
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;
266 return ParseServerResponse(server_url, response_body, position);
269 } // namespace
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) {
285 SimpleGeolocationRequest::~SimpleGeolocationRequest() {
286 DCHECK(thread_checker_.CalledOnValidThread());
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_);
295 void SimpleGeolocationRequest::StartRequest() {
296 DCHECK(thread_checker_.CalledOnValidThread());
297 RecordUmaEvent(SIMPLE_GEOLOCATION_REQUEST_EVENT_REQUEST_START);
298 ++retries_;
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();
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();
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);
329 void SimpleGeolocationRequest::OnURLFetchComplete(
330 const net::URLFetcher* source) {
331 DCHECK_EQ(url_fetcher_.get(), source);
333 net::URLRequestStatus status = source->GetStatus();
334 int response_code = source->GetResponseCode();
335 RecordUmaResponseCode(response_code);
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();
346 DVLOG(1) << "SimpleGeolocationRequest::OnURLFetchComplete(): position={"
347 << position_.ToString() << "}";
349 if (!success) {
350 Retry(server_error);
351 return;
353 const base::TimeDelta elapsed = base::Time::Now() - request_started_at_;
354 RecordUmaResponseTime(elapsed, success);
356 RecordUmaResult(SIMPLE_GEOLOCATION_REQUEST_RESULT_SUCCESS, retries_);
358 ReplyAndDestroySelf(elapsed, server_error);
359 // "this" is already destroyed here.
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();
369 ResponseCallback callback = callback_;
371 // Empty callback is used to identify "completed or not yet started request".
372 callback_.Reset();
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.
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.
392 } // namespace chromeos