Reland the patch to move WebGraphicsContext3DImpl out of webkit/.
[chromium-blink-merge.git] / chromeos / timezone / timezone_request.cc
blobc39de0b9d569e7fca1eca1c9442769650d821e8c
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/timezone/timezone_request.h"
7 #include <string>
9 #include "base/json/json_reader.h"
10 #include "base/metrics/histogram.h"
11 #include "base/metrics/sparse_histogram.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/time/time.h"
15 #include "base/values.h"
16 #include "chromeos/geolocation/geoposition.h"
17 #include "google_apis/google_api_keys.h"
18 #include "net/base/escape.h"
19 #include "net/base/load_flags.h"
20 #include "net/http/http_status_code.h"
21 #include "net/url_request/url_fetcher.h"
22 #include "net/url_request/url_request_context_getter.h"
23 #include "net/url_request/url_request_status.h"
25 namespace chromeos {
27 namespace {
29 const char kDefaultTimezoneProviderUrl[] =
30 "https://maps.googleapis.com/maps/api/timezone/json?";
32 const char kKeyString[] = "key";
33 // Language parameter is unsupported for now.
34 // const char kLanguageString[] = "language";
35 const char kLocationString[] = "location";
36 const char kSensorString[] = "sensor";
37 const char kTimestampString[] = "timestamp";
39 const char kDstOffsetString[] = "dstOffset";
40 const char kRawOffsetString[] = "rawOffset";
41 const char kTimeZoneIdString[] = "timeZoneId";
42 const char kTimeZoneNameString[] = "timeZoneName";
43 const char kStatusString[] = "status";
44 const char kErrorMessageString[] = "error_message";
46 // Sleep between timezone request retry on HTTP error.
47 const unsigned int kResolveTimeZoneRetrySleepOnServerErrorSeconds = 5;
49 // Sleep between timezone request retry on bad server response.
50 const unsigned int kResolveTimeZoneRetrySleepBadResponseSeconds = 10;
52 struct StatusString2Enum {
53 const char* string;
54 TimeZoneResponseData::Status value;
57 const StatusString2Enum statusString2Enum[] = {
58 {"OK", TimeZoneResponseData::OK},
59 {"INVALID_REQUEST", TimeZoneResponseData::INVALID_REQUEST},
60 {"OVER_QUERY_LIMIT", TimeZoneResponseData::OVER_QUERY_LIMIT},
61 {"REQUEST_DENIED", TimeZoneResponseData::REQUEST_DENIED},
62 {"UNKNOWN_ERROR", TimeZoneResponseData::UNKNOWN_ERROR},
63 {"ZERO_RESULTS", TimeZoneResponseData::ZERO_RESULTS}, };
65 enum TimeZoneRequestEvent {
66 // NOTE: Do not renumber these as that would confuse interpretation of
67 // previously logged data. When making changes, also update the enum list
68 // in tools/metrics/histograms/histograms.xml to keep it in sync.
69 TIMEZONE_REQUEST_EVENT_REQUEST_START = 0,
70 TIMEZONE_REQUEST_EVENT_RESPONSE_SUCCESS = 1,
71 TIMEZONE_REQUEST_EVENT_RESPONSE_NOT_OK = 2,
72 TIMEZONE_REQUEST_EVENT_RESPONSE_EMPTY = 3,
73 TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED = 4,
75 // NOTE: Add entries only immediately above this line.
76 TIMEZONE_REQUEST_EVENT_COUNT = 5
79 enum TimeZoneRequestResult {
80 // NOTE: Do not renumber these as that would confuse interpretation of
81 // previously logged data. When making changes, also update the enum list
82 // in tools/metrics/histograms/histograms.xml to keep it in sync.
83 TIMEZONE_REQUEST_RESULT_SUCCESS = 0,
84 TIMEZONE_REQUEST_RESULT_FAILURE = 1,
85 TIMEZONE_REQUEST_RESULT_SERVER_ERROR = 2,
86 TIMEZONE_REQUEST_RESULT_CANCELLED = 3,
88 // NOTE: Add entries only immediately above this line.
89 TIMEZONE_REQUEST_RESULT_COUNT = 4
92 // Too many requests (more than 1) mean there is a problem in implementation.
93 void RecordUmaEvent(TimeZoneRequestEvent event) {
94 UMA_HISTOGRAM_ENUMERATION(
95 "TimeZone.TimeZoneRequest.Event", event, TIMEZONE_REQUEST_EVENT_COUNT);
98 void RecordUmaResponseCode(int code) {
99 UMA_HISTOGRAM_SPARSE_SLOWLY("TimeZone.TimeZoneRequest.ResponseCode", code);
102 // Slow timezone resolve leads to bad user experience.
103 void RecordUmaResponseTime(base::TimeDelta elapsed, bool success) {
104 if (success) {
105 UMA_HISTOGRAM_TIMES("TimeZone.TimeZoneRequest.ResponseSuccessTime",
106 elapsed);
107 } else {
108 UMA_HISTOGRAM_TIMES("TimeZone.TimeZoneRequest.ResponseFailureTime",
109 elapsed);
113 void RecordUmaResult(TimeZoneRequestResult result, unsigned retries) {
114 UMA_HISTOGRAM_ENUMERATION(
115 "TimeZone.TimeZoneRequest.Result", result, TIMEZONE_REQUEST_RESULT_COUNT);
116 UMA_HISTOGRAM_SPARSE_SLOWLY("TimeZone.TimeZoneRequest.Retries", retries);
119 // Creates the request url to send to the server.
120 GURL TimeZoneRequestURL(const GURL& url,
121 const Geoposition& geoposition,
122 bool sensor) {
123 std::string query(url.query());
124 query += base::StringPrintf(
125 "%s=%f,%f", kLocationString, geoposition.latitude, geoposition.longitude);
126 if (url == DefaultTimezoneProviderURL()) {
127 std::string api_key = google_apis::GetAPIKey();
128 if (!api_key.empty()) {
129 query += "&";
130 query += kKeyString;
131 query += "=";
132 query += net::EscapeQueryParamValue(api_key, true);
135 if (!geoposition.timestamp.is_null()) {
136 query += base::StringPrintf(
137 "&%s=%ld", kTimestampString, geoposition.timestamp.ToTimeT());
139 query += "&";
140 query += kSensorString;
141 query += "=";
142 query += (sensor ? "true" : "false");
144 GURL::Replacements replacements;
145 replacements.SetQueryStr(query);
146 return url.ReplaceComponents(replacements);
149 void PrintTimeZoneError(const GURL& server_url,
150 const std::string& message,
151 TimeZoneResponseData* timezone) {
152 timezone->status = TimeZoneResponseData::REQUEST_ERROR;
153 timezone->error_message =
154 base::StringPrintf("TimeZone provider at '%s' : %s.",
155 server_url.GetOrigin().spec().c_str(),
156 message.c_str());
157 LOG(WARNING) << "TimeZoneRequest::GetTimeZoneFromResponse() : "
158 << timezone->error_message;
161 // Parses the server response body. Returns true if parsing was successful.
162 // Sets |*timezone| to the parsed TimeZone if a valid timezone was received,
163 // otherwise leaves it unchanged.
164 bool ParseServerResponse(const GURL& server_url,
165 const std::string& response_body,
166 TimeZoneResponseData* timezone) {
167 DCHECK(timezone);
169 if (response_body.empty()) {
170 PrintTimeZoneError(server_url, "Server returned empty response", timezone);
171 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_EMPTY);
172 return false;
174 VLOG(1) << "TimeZoneRequest::ParseServerResponse() : Parsing response "
175 << response_body;
177 // Parse the response, ignoring comments.
178 std::string error_msg;
179 scoped_ptr<base::Value> response_value(base::JSONReader::ReadAndReturnError(
180 response_body, base::JSON_PARSE_RFC, NULL, &error_msg));
181 if (response_value == NULL) {
182 PrintTimeZoneError(server_url, "JSONReader failed: " + error_msg, timezone);
183 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
184 return false;
187 const base::DictionaryValue* response_object = NULL;
188 if (!response_value->GetAsDictionary(&response_object)) {
189 PrintTimeZoneError(server_url,
190 "Unexpected response type : " +
191 base::StringPrintf("%u", response_value->GetType()),
192 timezone);
193 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
194 return false;
197 std::string status;
199 if (!response_object->GetStringWithoutPathExpansion(kStatusString, &status)) {
200 PrintTimeZoneError(server_url, "Missing status attribute.", timezone);
201 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
202 return false;
205 bool found = false;
206 for (size_t i = 0; i < arraysize(statusString2Enum); ++i) {
207 if (status != statusString2Enum[i].string)
208 continue;
210 timezone->status = statusString2Enum[i].value;
211 found = true;
212 break;
215 if (!found) {
216 PrintTimeZoneError(
217 server_url, "Bad status attribute value: '" + status + "'", timezone);
218 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
219 return false;
222 const bool status_ok = (timezone->status == TimeZoneResponseData::OK);
224 if (!response_object->GetDoubleWithoutPathExpansion(kDstOffsetString,
225 &timezone->dstOffset) &&
226 status_ok) {
227 PrintTimeZoneError(server_url, "Missing dstOffset attribute.", timezone);
228 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
229 return false;
232 if (!response_object->GetDoubleWithoutPathExpansion(kRawOffsetString,
233 &timezone->rawOffset) &&
234 status_ok) {
235 PrintTimeZoneError(server_url, "Missing rawOffset attribute.", timezone);
236 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
237 return false;
240 if (!response_object->GetStringWithoutPathExpansion(kTimeZoneIdString,
241 &timezone->timeZoneId) &&
242 status_ok) {
243 PrintTimeZoneError(server_url, "Missing timeZoneId attribute.", timezone);
244 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
245 return false;
248 if (!response_object->GetStringWithoutPathExpansion(
249 kTimeZoneNameString, &timezone->timeZoneName) &&
250 status_ok) {
251 PrintTimeZoneError(server_url, "Missing timeZoneName attribute.", timezone);
252 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
253 return false;
256 // "error_message" field is optional. Ignore result.
257 response_object->GetStringWithoutPathExpansion(kErrorMessageString,
258 &timezone->error_message);
260 return true;
263 // Attempts to extract a position from the response. Detects and indicates
264 // various failure cases.
265 scoped_ptr<TimeZoneResponseData> GetTimeZoneFromResponse(
266 bool http_success,
267 int status_code,
268 const std::string& response_body,
269 const GURL& server_url) {
270 scoped_ptr<TimeZoneResponseData> timezone(new TimeZoneResponseData);
272 // HttpPost can fail for a number of reasons. Most likely this is because
273 // we're offline, or there was no response.
274 if (!http_success) {
275 PrintTimeZoneError(server_url, "No response received", timezone.get());
276 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_EMPTY);
277 return timezone.Pass();
279 if (status_code != net::HTTP_OK) {
280 std::string message = "Returned error code ";
281 message += base::IntToString(status_code);
282 PrintTimeZoneError(server_url, message, timezone.get());
283 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_NOT_OK);
284 return timezone.Pass();
287 if (!ParseServerResponse(server_url, response_body, timezone.get()))
288 return timezone.Pass();
290 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_SUCCESS);
291 return timezone.Pass();
294 } // namespace
296 TimeZoneResponseData::TimeZoneResponseData()
297 : dstOffset(0), rawOffset(0), status(ZERO_RESULTS) {
300 GURL DefaultTimezoneProviderURL() {
301 return GURL(kDefaultTimezoneProviderUrl);
304 TimeZoneRequest::TimeZoneRequest(
305 net::URLRequestContextGetter* url_context_getter,
306 const GURL& service_url,
307 const Geoposition& geoposition,
308 bool sensor,
309 base::TimeDelta retry_timeout)
310 : url_context_getter_(url_context_getter),
311 service_url_(service_url),
312 geoposition_(geoposition),
313 sensor_(sensor),
314 retry_timeout_abs_(base::Time::Now() + retry_timeout),
315 retry_sleep_on_server_error_(base::TimeDelta::FromSeconds(
316 kResolveTimeZoneRetrySleepOnServerErrorSeconds)),
317 retry_sleep_on_bad_response_(base::TimeDelta::FromSeconds(
318 kResolveTimeZoneRetrySleepBadResponseSeconds)),
319 retries_(0) {
322 TimeZoneRequest::~TimeZoneRequest() {
323 DCHECK(thread_checker_.CalledOnValidThread());
325 // If callback is not empty, request is cancelled.
326 if (!callback_.is_null()) {
327 RecordUmaResponseTime(base::Time::Now() - request_started_at_, false);
328 RecordUmaResult(TIMEZONE_REQUEST_RESULT_CANCELLED, retries_);
332 void TimeZoneRequest::StartRequest() {
333 DCHECK(thread_checker_.CalledOnValidThread());
334 RecordUmaEvent(TIMEZONE_REQUEST_EVENT_REQUEST_START);
335 request_started_at_ = base::Time::Now();
336 ++retries_;
338 url_fetcher_.reset(
339 net::URLFetcher::Create(request_url_, net::URLFetcher::GET, this));
340 url_fetcher_->SetRequestContext(url_context_getter_.get());
341 url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
342 net::LOAD_DISABLE_CACHE |
343 net::LOAD_DO_NOT_SAVE_COOKIES |
344 net::LOAD_DO_NOT_SEND_COOKIES |
345 net::LOAD_DO_NOT_SEND_AUTH_DATA);
346 url_fetcher_->Start();
349 void TimeZoneRequest::MakeRequest(TimeZoneResponseCallback callback) {
350 callback_ = callback;
351 request_url_ =
352 TimeZoneRequestURL(service_url_, geoposition_, false /* sensor */);
353 StartRequest();
356 void TimeZoneRequest::Retry(bool server_error) {
357 const base::TimeDelta delay(server_error ? retry_sleep_on_server_error_
358 : retry_sleep_on_bad_response_);
359 timezone_request_scheduled_.Start(
360 FROM_HERE, delay, this, &TimeZoneRequest::StartRequest);
363 void TimeZoneRequest::OnURLFetchComplete(const net::URLFetcher* source) {
364 DCHECK_EQ(url_fetcher_.get(), source);
366 net::URLRequestStatus status = source->GetStatus();
367 int response_code = source->GetResponseCode();
368 RecordUmaResponseCode(response_code);
370 std::string data;
371 source->GetResponseAsString(&data);
372 scoped_ptr<TimeZoneResponseData> timezone = GetTimeZoneFromResponse(
373 status.is_success(), response_code, data, source->GetURL());
374 const bool server_error =
375 !status.is_success() || (response_code >= 500 && response_code < 600);
376 url_fetcher_.reset();
378 DVLOG(1) << "TimeZoneRequest::OnURLFetchComplete(): timezone={"
379 << timezone->ToStringForDebug() << "}";
381 const base::Time now = base::Time::Now();
382 const bool retry_timeout = (now >= retry_timeout_abs_);
384 const bool success = (timezone->status == TimeZoneResponseData::OK);
385 if (!success && !retry_timeout) {
386 Retry(server_error);
387 return;
389 RecordUmaResponseTime(base::Time::Now() - request_started_at_, success);
391 const TimeZoneRequestResult result =
392 (server_error ? TIMEZONE_REQUEST_RESULT_SERVER_ERROR
393 : (success ? TIMEZONE_REQUEST_RESULT_SUCCESS
394 : TIMEZONE_REQUEST_RESULT_FAILURE));
395 RecordUmaResult(result, retries_);
397 TimeZoneResponseCallback callback = callback_;
399 // Empty callback is used to identify "completed or not yet started request".
400 callback_.Reset();
402 // callback.Run() usually destroys TimeZoneRequest, because this is the way
403 // callback is implemented in TimeZoneProvider.
404 callback.Run(timezone.Pass(), server_error);
405 // "this" is already destroyed here.
408 std::string TimeZoneResponseData::ToStringForDebug() const {
409 static const char* const status2string[] = {
410 "OK",
411 "INVALID_REQUEST",
412 "OVER_QUERY_LIMIT",
413 "REQUEST_DENIED",
414 "UNKNOWN_ERROR",
415 "ZERO_RESULTS",
416 "REQUEST_ERROR"
419 return base::StringPrintf(
420 "dstOffset=%f, rawOffset=%f, timeZoneId='%s', timeZoneName='%s', "
421 "error_message='%s', status=%u (%s)",
422 dstOffset,
423 rawOffset,
424 timeZoneId.c_str(),
425 timeZoneName.c_str(),
426 error_message.c_str(),
427 (unsigned)status,
428 (status < arraysize(status2string) ? status2string[status] : "unknown"));
431 } // namespace chromeos