privet_url_fetcher.cc revision 0f1bc08d4cfcc34181b0b5cbf065c40f687bf740
1// Copyright 2013 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/local_discovery/privet_url_fetcher.h"
6
7#include "base/bind.h"
8#include "base/json/json_reader.h"
9#include "base/message_loop/message_loop.h"
10#include "base/rand_util.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/local_discovery/privet_constants.h"
13#include "net/http/http_status_code.h"
14#include "net/url_request/url_request_status.h"
15
16namespace local_discovery {
17
18namespace {
19const char kXPrivetTokenHeaderPrefix[] = "X-Privet-Token: ";
20const char kXPrivetEmptyToken[] = "\"\"";
21const int kPrivetMaxRetries = 20;
22}
23
24void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
25    PrivetURLFetcher* fetcher,
26    const TokenCallback& callback) {
27  OnError(fetcher, TOKEN_ERROR);
28}
29
30PrivetURLFetcher::PrivetURLFetcher(
31    const std::string& token,
32    const GURL& url,
33    net::URLFetcher::RequestType request_type,
34    net::URLRequestContextGetter* request_context,
35    PrivetURLFetcher::Delegate* delegate)
36    : privet_access_token_(token), url_(url), request_type_(request_type),
37      request_context_(request_context), delegate_(delegate),
38      do_not_retry_on_transient_error_(false), allow_empty_privet_token_(false),
39      tries_(0), weak_factory_(this) {
40}
41
42PrivetURLFetcher::~PrivetURLFetcher() {
43}
44
45void PrivetURLFetcher::DoNotRetryOnTransientError() {
46  do_not_retry_on_transient_error_ = true;
47}
48
49void PrivetURLFetcher::AllowEmptyPrivetToken() {
50  allow_empty_privet_token_ = true;
51}
52
53void PrivetURLFetcher::Try() {
54  tries_++;
55  if (tries_ < kPrivetMaxRetries) {
56    std::string token = privet_access_token_;
57
58    if (token.empty())
59      token = kXPrivetEmptyToken;
60
61    url_fetcher_.reset(net::URLFetcher::Create(url_, request_type_, this));
62    url_fetcher_->SetRequestContext(request_context_);
63    url_fetcher_->AddExtraRequestHeader(std::string(kXPrivetTokenHeaderPrefix) +
64                                        token);
65
66    // URLFetcher requires us to set upload data for POST requests.
67    if (request_type_ == net::URLFetcher::POST)
68      url_fetcher_->SetUploadData(upload_content_type_, upload_data_);
69
70    url_fetcher_->Start();
71  } else {
72    delegate_->OnError(this, RETRY_ERROR);
73  }
74}
75
76void PrivetURLFetcher::Start() {
77  DCHECK_EQ(tries_, 0);  // We haven't called |Start()| yet.
78
79  if (privet_access_token_.empty() && !allow_empty_privet_token_) {
80    RequestTokenRefresh();
81  } else {
82    Try();
83  }
84}
85
86void PrivetURLFetcher::SetUploadData(const std::string& upload_content_type,
87                                     const std::string& upload_data) {
88  upload_content_type_ = upload_content_type;
89  upload_data_ = upload_data;
90}
91
92void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
93  if (source->GetStatus().status() != net::URLRequestStatus::SUCCESS) {
94    delegate_->OnError(this, URL_FETCH_ERROR);
95    return;
96  }
97
98  if (source->GetResponseCode() != net::HTTP_OK) {
99    delegate_->OnError(this, RESPONSE_CODE_ERROR);
100    return;
101  }
102
103  std::string response_str;
104
105  if (!source->GetResponseAsString(&response_str)) {
106    delegate_->OnError(this, URL_FETCH_ERROR);
107    return;
108  }
109
110  base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS);
111  scoped_ptr<base::Value> value;
112
113  value.reset(json_reader.ReadToValue(response_str));
114
115  if (!value) {
116    delegate_->OnError(this, JSON_PARSE_ERROR);
117    return;
118  }
119
120  const base::DictionaryValue* dictionary_value;
121
122  if (!value->GetAsDictionary(&dictionary_value)) {
123    delegate_->OnError(this, JSON_PARSE_ERROR);
124    return;
125  }
126
127  std::string error;
128  if (dictionary_value->GetString(kPrivetKeyError, &error)) {
129    if (error == kPrivetErrorInvalidXPrivetToken) {
130      RequestTokenRefresh();
131      return;
132    } else if (PrivetErrorTransient(error) ||
133        dictionary_value->HasKey(kPrivetKeyTimeout)) {
134      if (!do_not_retry_on_transient_error_) {
135        int timeout_seconds;
136        if (!dictionary_value->GetInteger(kPrivetKeyTimeout,
137                                          &timeout_seconds)) {
138          timeout_seconds = kPrivetDefaultTimeout;
139        }
140
141        ScheduleRetry(timeout_seconds);
142        return;
143      }
144    }
145  }
146
147  delegate_->OnParsedJson(this, dictionary_value,
148                          dictionary_value->HasKey(kPrivetKeyError));
149}
150
151void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) {
152  double random_scaling_factor =
153      1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
154
155  int timeout_seconds_randomized =
156      static_cast<int>(timeout_seconds * random_scaling_factor);
157
158  base::MessageLoop::current()->PostDelayedTask(
159      FROM_HERE,
160      base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()),
161      base::TimeDelta::FromSeconds(timeout_seconds_randomized));
162}
163
164void PrivetURLFetcher::RequestTokenRefresh() {
165  delegate_->OnNeedPrivetToken(
166      this,
167      base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
168}
169
170void PrivetURLFetcher::RefreshToken(const std::string& token) {
171  if (token.empty()) {
172    delegate_->OnError(this, TOKEN_ERROR);
173  } else {
174    privet_access_token_ = token;
175    Try();
176  }
177}
178
179bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) {
180  return (error == kPrivetErrorDeviceBusy) ||
181         (error == kPrivetErrorPendingUserAction);
182}
183
184PrivetURLFetcherFactory::PrivetURLFetcherFactory(
185    net::URLRequestContextGetter* request_context)
186    : request_context_(request_context) {
187}
188
189PrivetURLFetcherFactory::~PrivetURLFetcherFactory() {
190}
191
192scoped_ptr<PrivetURLFetcher> PrivetURLFetcherFactory::CreateURLFetcher(
193    const GURL& url, net::URLFetcher::RequestType request_type,
194    PrivetURLFetcher::Delegate* delegate) const {
195  return scoped_ptr<PrivetURLFetcher>(
196      new PrivetURLFetcher(token_, url, request_type, request_context_.get(),
197                           delegate));
198}
199
200}  // namespace local_discovery
201