1 // Copyright (c) 2012 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/service/cloud_print/cloud_print_url_fetcher.h"
6
7 #include "base/metrics/histogram.h"
8 #include "base/strings/stringprintf.h"
9 #include "base/values.h"
10 #include "chrome/common/cloud_print/cloud_print_constants.h"
11 #include "chrome/common/cloud_print/cloud_print_helpers.h"
12 #include "chrome/service/cloud_print/cloud_print_service_helpers.h"
13 #include "chrome/service/cloud_print/cloud_print_token_store.h"
14 #include "chrome/service/net/service_url_request_context_getter.h"
15 #include "chrome/service/service_process.h"
16 #include "net/base/load_flags.h"
17 #include "net/http/http_status_code.h"
18 #include "net/url_request/url_fetcher.h"
19 #include "net/url_request/url_request_status.h"
20 #include "url/gurl.h"
21
22 namespace cloud_print {
23
24 namespace {
25
ReportRequestTime(CloudPrintURLFetcher::RequestType type,base::TimeDelta time)26 void ReportRequestTime(CloudPrintURLFetcher::RequestType type,
27 base::TimeDelta time) {
28 if (type == CloudPrintURLFetcher::REQUEST_REGISTER) {
29 UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.Register", time);
30 } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) {
31 UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.UpdatePrinter", time);
32 } else if (type == CloudPrintURLFetcher::REQUEST_DATA) {
33 UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.DownloadData", time);
34 } else {
35 UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.Other", time);
36 }
37 }
38
ReportRetriesCount(CloudPrintURLFetcher::RequestType type,int retries)39 void ReportRetriesCount(CloudPrintURLFetcher::RequestType type,
40 int retries) {
41 if (type == CloudPrintURLFetcher::REQUEST_REGISTER) {
42 UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.Register", retries);
43 } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) {
44 UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.UpdatePrinter",
45 retries);
46 } else if (type == CloudPrintURLFetcher::REQUEST_DATA) {
47 UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.DownloadData",
48 retries);
49 } else {
50 UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.Other", retries);
51 }
52 }
53
ReportDownloadSize(CloudPrintURLFetcher::RequestType type,size_t size)54 void ReportDownloadSize(CloudPrintURLFetcher::RequestType type, size_t size) {
55 if (type == CloudPrintURLFetcher::REQUEST_REGISTER) {
56 UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.Register", size);
57 } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) {
58 UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.UpdatePrinter",
59 size);
60 } else if (type == CloudPrintURLFetcher::REQUEST_DATA) {
61 UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.DownloadData",
62 size);
63 } else {
64 UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.Other", size);
65 }
66 }
67
ReportUploadSize(CloudPrintURLFetcher::RequestType type,size_t size)68 void ReportUploadSize(CloudPrintURLFetcher::RequestType type, size_t size) {
69 if (type == CloudPrintURLFetcher::REQUEST_REGISTER) {
70 UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.Register", size);
71 } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) {
72 UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.UpdatePrinter",
73 size);
74 } else if (type == CloudPrintURLFetcher::REQUEST_DATA) {
75 UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.DownloadData",
76 size);
77 } else {
78 UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.Other", size);
79 }
80 }
81
82 CloudPrintURLFetcherFactory* g_factory = NULL;
83
84 } // namespace
85
86 // virtual
~CloudPrintURLFetcherFactory()87 CloudPrintURLFetcherFactory::~CloudPrintURLFetcherFactory() {}
88
89 // static
Create()90 CloudPrintURLFetcher* CloudPrintURLFetcher::Create() {
91 CloudPrintURLFetcherFactory* factory = CloudPrintURLFetcher::factory();
92 return factory ? factory->CreateCloudPrintURLFetcher() :
93 new CloudPrintURLFetcher;
94 }
95
96 // static
factory()97 CloudPrintURLFetcherFactory* CloudPrintURLFetcher::factory() {
98 return g_factory;
99 }
100
101 // static
set_factory(CloudPrintURLFetcherFactory * factory)102 void CloudPrintURLFetcher::set_factory(CloudPrintURLFetcherFactory* factory) {
103 g_factory = factory;
104 }
105
106 CloudPrintURLFetcher::ResponseAction
HandleRawResponse(const net::URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const net::ResponseCookies & cookies,const std::string & data)107 CloudPrintURLFetcher::Delegate::HandleRawResponse(
108 const net::URLFetcher* source,
109 const GURL& url,
110 const net::URLRequestStatus& status,
111 int response_code,
112 const net::ResponseCookies& cookies,
113 const std::string& data) {
114 return CONTINUE_PROCESSING;
115 }
116
117 CloudPrintURLFetcher::ResponseAction
HandleRawData(const net::URLFetcher * source,const GURL & url,const std::string & data)118 CloudPrintURLFetcher::Delegate::HandleRawData(
119 const net::URLFetcher* source,
120 const GURL& url,
121 const std::string& data) {
122 return CONTINUE_PROCESSING;
123 }
124
125 CloudPrintURLFetcher::ResponseAction
HandleJSONData(const net::URLFetcher * source,const GURL & url,base::DictionaryValue * json_data,bool succeeded)126 CloudPrintURLFetcher::Delegate::HandleJSONData(
127 const net::URLFetcher* source,
128 const GURL& url,
129 base::DictionaryValue* json_data,
130 bool succeeded) {
131 return CONTINUE_PROCESSING;
132 }
133
CloudPrintURLFetcher()134 CloudPrintURLFetcher::CloudPrintURLFetcher()
135 : delegate_(NULL),
136 num_retries_(0),
137 type_(REQUEST_MAX) {
138 }
139
IsSameRequest(const net::URLFetcher * source)140 bool CloudPrintURLFetcher::IsSameRequest(const net::URLFetcher* source) {
141 return (request_.get() == source);
142 }
143
StartGetRequest(RequestType type,const GURL & url,Delegate * delegate,int max_retries,const std::string & additional_headers)144 void CloudPrintURLFetcher::StartGetRequest(
145 RequestType type,
146 const GURL& url,
147 Delegate* delegate,
148 int max_retries,
149 const std::string& additional_headers) {
150 StartRequestHelper(type, url, net::URLFetcher::GET, delegate, max_retries,
151 std::string(), std::string(), additional_headers);
152 }
153
StartPostRequest(RequestType type,const GURL & url,Delegate * delegate,int max_retries,const std::string & post_data_mime_type,const std::string & post_data,const std::string & additional_headers)154 void CloudPrintURLFetcher::StartPostRequest(
155 RequestType type,
156 const GURL& url,
157 Delegate* delegate,
158 int max_retries,
159 const std::string& post_data_mime_type,
160 const std::string& post_data,
161 const std::string& additional_headers) {
162 StartRequestHelper(type, url, net::URLFetcher::POST, delegate, max_retries,
163 post_data_mime_type, post_data, additional_headers);
164 }
165
OnURLFetchComplete(const net::URLFetcher * source)166 void CloudPrintURLFetcher::OnURLFetchComplete(
167 const net::URLFetcher* source) {
168 VLOG(1) << "CP_PROXY: OnURLFetchComplete, url: " << source->GetURL()
169 << ", response code: " << source->GetResponseCode();
170 // Make sure we stay alive through the body of this function.
171 scoped_refptr<CloudPrintURLFetcher> keep_alive(this);
172 std::string data;
173 source->GetResponseAsString(&data);
174 ReportRequestTime(type_, base::Time::Now() - start_time_);
175 ReportDownloadSize(type_, data.size());
176 ResponseAction action = delegate_->HandleRawResponse(
177 source,
178 source->GetURL(),
179 source->GetStatus(),
180 source->GetResponseCode(),
181 source->GetCookies(),
182 data);
183
184 // If we get auth error, notify delegate and check if it wants to proceed.
185 if (action == CONTINUE_PROCESSING &&
186 source->GetResponseCode() == net::HTTP_FORBIDDEN) {
187 action = delegate_->OnRequestAuthError();
188 }
189
190 if (action == CONTINUE_PROCESSING) {
191 // We need to retry on all network errors.
192 if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200))
193 action = RETRY_REQUEST;
194 else
195 action = delegate_->HandleRawData(source, source->GetURL(), data);
196
197 if (action == CONTINUE_PROCESSING) {
198 // If the delegate is not interested in handling the raw response data,
199 // we assume that a JSON response is expected. If we do not get a JSON
200 // response, we will retry (to handle the case where we got redirected
201 // to a non-cloudprint-server URL eg. for authentication).
202 bool succeeded = false;
203 scoped_ptr<base::DictionaryValue> response_dict =
204 ParseResponseJSON(data, &succeeded);
205
206 if (response_dict) {
207 action = delegate_->HandleJSONData(source,
208 source->GetURL(),
209 response_dict.get(),
210 succeeded);
211 } else {
212 action = RETRY_REQUEST;
213 }
214 }
215 }
216 // Retry the request if needed.
217 if (action == RETRY_REQUEST) {
218 // Explicitly call ReceivedContentWasMalformed() to ensure the current
219 // request gets counted as a failure for calculation of the back-off
220 // period. If it was already a failure by status code, this call will
221 // be ignored.
222 request_->ReceivedContentWasMalformed();
223
224 // If we receive error code from the server "Media Type Not Supported",
225 // there is no reason to retry, request will never succeed.
226 // In that case we should call OnRequestGiveUp() right away.
227 if (source->GetResponseCode() == net::HTTP_UNSUPPORTED_MEDIA_TYPE)
228 num_retries_ = source->GetMaxRetriesOn5xx();
229
230 ++num_retries_;
231 if ((-1 != source->GetMaxRetriesOn5xx()) &&
232 (num_retries_ > source->GetMaxRetriesOn5xx())) {
233 // Retry limit reached. Give up.
234 delegate_->OnRequestGiveUp();
235 action = STOP_PROCESSING;
236 } else {
237 // Either no retry limit specified or retry limit has not yet been
238 // reached. Try again. Set up the request headers again because the token
239 // may have changed.
240 SetupRequestHeaders();
241 request_->SetRequestContext(GetRequestContextGetter());
242 start_time_ = base::Time::Now();
243 request_->Start();
244 }
245 }
246 if (action != RETRY_REQUEST) {
247 ReportRetriesCount(type_, num_retries_);
248 }
249 }
250
StartRequestHelper(RequestType type,const GURL & url,net::URLFetcher::RequestType request_type,Delegate * delegate,int max_retries,const std::string & post_data_mime_type,const std::string & post_data,const std::string & additional_headers)251 void CloudPrintURLFetcher::StartRequestHelper(
252 RequestType type,
253 const GURL& url,
254 net::URLFetcher::RequestType request_type,
255 Delegate* delegate,
256 int max_retries,
257 const std::string& post_data_mime_type,
258 const std::string& post_data,
259 const std::string& additional_headers) {
260 DCHECK(delegate);
261 type_ = type;
262 UMA_HISTOGRAM_ENUMERATION("CloudPrint.UrlFetcherRequestType", type,
263 REQUEST_MAX);
264 // Persist the additional headers in case we need to retry the request.
265 additional_headers_ = additional_headers;
266 request_.reset(net::URLFetcher::Create(0, url, request_type, this));
267 request_->SetRequestContext(GetRequestContextGetter());
268 // Since we implement our own retry logic, disable the retry in URLFetcher.
269 request_->SetAutomaticallyRetryOn5xx(false);
270 request_->SetMaxRetriesOn5xx(max_retries);
271 delegate_ = delegate;
272 SetupRequestHeaders();
273 request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
274 net::LOAD_DO_NOT_SAVE_COOKIES);
275 if (request_type == net::URLFetcher::POST) {
276 request_->SetUploadData(post_data_mime_type, post_data);
277 ReportUploadSize(type_, post_data.size());
278 }
279 start_time_ = base::Time::Now();
280 request_->Start();
281 }
282
SetupRequestHeaders()283 void CloudPrintURLFetcher::SetupRequestHeaders() {
284 std::string headers = delegate_->GetAuthHeader();
285 if (!headers.empty())
286 headers += "\r\n";
287 headers += kChromeCloudPrintProxyHeader;
288 if (!additional_headers_.empty()) {
289 headers += "\r\n";
290 headers += additional_headers_;
291 }
292 request_->SetExtraRequestHeaders(headers);
293 }
294
~CloudPrintURLFetcher()295 CloudPrintURLFetcher::~CloudPrintURLFetcher() {}
296
GetRequestContextGetter()297 net::URLRequestContextGetter* CloudPrintURLFetcher::GetRequestContextGetter() {
298 ServiceURLRequestContextGetter* getter =
299 g_service_process->GetServiceURLRequestContextGetter();
300 // Now set up the user agent for cloudprint.
301 std::string user_agent = getter->user_agent();
302 base::StringAppendF(&user_agent, " %s", kCloudPrintUserAgent);
303 getter->set_user_agent(user_agent);
304 return getter;
305 }
306
307 } // namespace cloud_print
308