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.
5 #include "chrome/browser/local_discovery/privet_url_fetcher.h"
10 #include "base/json/json_reader.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/rand_util.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/local_discovery/privet_constants.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "net/http/http_status_code.h"
17 #include "net/url_request/url_request_status.h"
19 namespace local_discovery
{
22 const char kXPrivetTokenHeaderPrefix
[] = "X-Privet-Token: ";
23 const char kXPrivetEmptyToken
[] = "\"\"";
24 const int kPrivetMaxRetries
= 20;
25 const int kPrivetTimeoutOnError
= 5;
28 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
29 PrivetURLFetcher
* fetcher
,
30 const TokenCallback
& callback
) {
31 OnError(fetcher
, TOKEN_ERROR
);
34 PrivetURLFetcher::PrivetURLFetcher(
35 const std::string
& token
,
37 net::URLFetcher::RequestType request_type
,
38 net::URLRequestContextGetter
* request_context
,
39 PrivetURLFetcher::Delegate
* delegate
)
40 : privet_access_token_(token
), url_(url
), request_type_(request_type
),
41 request_context_(request_context
), delegate_(delegate
),
42 do_not_retry_on_transient_error_(false), allow_empty_privet_token_(false),
43 tries_(0), weak_factory_(this) {
46 PrivetURLFetcher::~PrivetURLFetcher() {
49 void PrivetURLFetcher::DoNotRetryOnTransientError() {
50 do_not_retry_on_transient_error_
= true;
53 void PrivetURLFetcher::AllowEmptyPrivetToken() {
54 allow_empty_privet_token_
= true;
57 void PrivetURLFetcher::Try() {
59 if (tries_
< kPrivetMaxRetries
) {
60 std::string token
= privet_access_token_
;
63 token
= kXPrivetEmptyToken
;
65 url_fetcher_
.reset(net::URLFetcher::Create(url_
, request_type_
, this));
66 url_fetcher_
->SetRequestContext(request_context_
);
67 url_fetcher_
->AddExtraRequestHeader(std::string(kXPrivetTokenHeaderPrefix
) +
70 // URLFetcher requires us to set upload data for POST requests.
71 if (request_type_
== net::URLFetcher::POST
) {
72 if (!upload_file_path_
.empty()) {
73 url_fetcher_
->SetUploadFilePath(
77 kuint64max
/*length*/,
78 content::BrowserThread::GetMessageLoopProxyForThread(
79 content::BrowserThread::FILE));
81 url_fetcher_
->SetUploadData(upload_content_type_
, upload_data_
);
85 url_fetcher_
->Start();
87 delegate_
->OnError(this, RETRY_ERROR
);
91 void PrivetURLFetcher::Start() {
92 DCHECK_EQ(tries_
, 0); // We haven't called |Start()| yet.
94 if (privet_access_token_
.empty() && !allow_empty_privet_token_
) {
95 RequestTokenRefresh();
101 void PrivetURLFetcher::SetUploadData(const std::string
& upload_content_type
,
102 const std::string
& upload_data
) {
103 DCHECK(upload_file_path_
.empty());
104 upload_content_type_
= upload_content_type
;
105 upload_data_
= upload_data
;
108 void PrivetURLFetcher::SetUploadFilePath(
109 const std::string
& upload_content_type
,
110 const base::FilePath
& upload_file_path
) {
111 DCHECK(upload_data_
.empty());
112 upload_content_type_
= upload_content_type
;
113 upload_file_path_
= upload_file_path
;
116 void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher
* source
) {
117 if (source
->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE
) {
118 ScheduleRetry(kPrivetTimeoutOnError
);
122 if (source
->GetResponseCode() != net::HTTP_OK
) {
123 delegate_
->OnError(this, RESPONSE_CODE_ERROR
);
127 std::string response_str
;
129 if (!source
->GetResponseAsString(&response_str
)) {
130 delegate_
->OnError(this, URL_FETCH_ERROR
);
134 base::JSONReader
json_reader(base::JSON_ALLOW_TRAILING_COMMAS
);
135 scoped_ptr
<base::Value
> value
;
137 value
.reset(json_reader
.ReadToValue(response_str
));
140 delegate_
->OnError(this, JSON_PARSE_ERROR
);
144 const base::DictionaryValue
* dictionary_value
;
146 if (!value
->GetAsDictionary(&dictionary_value
)) {
147 delegate_
->OnError(this, JSON_PARSE_ERROR
);
152 if (dictionary_value
->GetString(kPrivetKeyError
, &error
)) {
153 if (error
== kPrivetErrorInvalidXPrivetToken
) {
154 RequestTokenRefresh();
156 } else if (PrivetErrorTransient(error
)) {
157 if (!do_not_retry_on_transient_error_
) {
159 if (!dictionary_value
->GetInteger(kPrivetKeyTimeout
,
161 timeout_seconds
= kPrivetDefaultTimeout
;
164 ScheduleRetry(timeout_seconds
);
170 delegate_
->OnParsedJson(this, dictionary_value
,
171 dictionary_value
->HasKey(kPrivetKeyError
));
174 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds
) {
175 double random_scaling_factor
=
176 1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition
;
178 int timeout_seconds_randomized
=
179 static_cast<int>(timeout_seconds
* random_scaling_factor
);
181 timeout_seconds_randomized
=
182 std::max(timeout_seconds_randomized
, kPrivetMinimumTimeout
);
184 base::MessageLoop::current()->PostDelayedTask(
186 base::Bind(&PrivetURLFetcher::Try
, weak_factory_
.GetWeakPtr()),
187 base::TimeDelta::FromSeconds(timeout_seconds_randomized
));
190 void PrivetURLFetcher::RequestTokenRefresh() {
191 delegate_
->OnNeedPrivetToken(
193 base::Bind(&PrivetURLFetcher::RefreshToken
, weak_factory_
.GetWeakPtr()));
196 void PrivetURLFetcher::RefreshToken(const std::string
& token
) {
198 delegate_
->OnError(this, TOKEN_ERROR
);
200 privet_access_token_
= token
;
205 bool PrivetURLFetcher::PrivetErrorTransient(const std::string
& error
) {
206 return (error
== kPrivetErrorDeviceBusy
) ||
207 (error
== kPrivetErrorPendingUserAction
) ||
208 (error
== kPrivetErrorPrinterBusy
);
211 PrivetURLFetcherFactory::PrivetURLFetcherFactory(
212 net::URLRequestContextGetter
* request_context
)
213 : request_context_(request_context
) {
216 PrivetURLFetcherFactory::~PrivetURLFetcherFactory() {
219 scoped_ptr
<PrivetURLFetcher
> PrivetURLFetcherFactory::CreateURLFetcher(
220 const GURL
& url
, net::URLFetcher::RequestType request_type
,
221 PrivetURLFetcher::Delegate
* delegate
) const {
222 return scoped_ptr
<PrivetURLFetcher
>(
223 new PrivetURLFetcher(token_
, url
, request_type
, request_context_
.get(),
227 } // namespace local_discovery