// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/service/cloud_print/cloud_print_url_fetcher.h" #include #include "base/metrics/histogram.h" #include "base/strings/stringprintf.h" #include "base/values.h" #include "chrome/common/cloud_print/cloud_print_constants.h" #include "chrome/common/cloud_print/cloud_print_helpers.h" #include "chrome/service/cloud_print/cloud_print_service_helpers.h" #include "chrome/service/cloud_print/cloud_print_token_store.h" #include "chrome/service/net/service_url_request_context_getter.h" #include "chrome/service/service_process.h" #include "net/base/load_flags.h" #include "net/http/http_status_code.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_request_status.h" #include "url/gurl.h" namespace cloud_print { namespace { void ReportRequestTime(CloudPrintURLFetcher::RequestType type, base::TimeDelta time) { if (type == CloudPrintURLFetcher::REQUEST_REGISTER) { UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.Register", time); } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) { UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.UpdatePrinter", time); } else if (type == CloudPrintURLFetcher::REQUEST_DATA) { UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.DownloadData", time); } else { UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.Other", time); } } void ReportRetriesCount(CloudPrintURLFetcher::RequestType type, int retries) { if (type == CloudPrintURLFetcher::REQUEST_REGISTER) { UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.Register", retries); } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) { UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.UpdatePrinter", retries); } else if (type == CloudPrintURLFetcher::REQUEST_DATA) { UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.DownloadData", retries); } else { UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.Other", retries); } } void ReportDownloadSize(CloudPrintURLFetcher::RequestType type, size_t size) { if (type == CloudPrintURLFetcher::REQUEST_REGISTER) { UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.Register", size); } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) { UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.UpdatePrinter", size); } else if (type == CloudPrintURLFetcher::REQUEST_DATA) { UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.DownloadData", size); } else { UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.Other", size); } } void ReportUploadSize(CloudPrintURLFetcher::RequestType type, size_t size) { if (type == CloudPrintURLFetcher::REQUEST_REGISTER) { UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.Register", size); } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) { UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.UpdatePrinter", size); } else if (type == CloudPrintURLFetcher::REQUEST_DATA) { UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.DownloadData", size); } else { UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.Other", size); } } CloudPrintURLFetcherFactory* g_factory = NULL; } // namespace // virtual CloudPrintURLFetcherFactory::~CloudPrintURLFetcherFactory() {} // static CloudPrintURLFetcher* CloudPrintURLFetcher::Create() { CloudPrintURLFetcherFactory* factory = CloudPrintURLFetcher::factory(); return factory ? factory->CreateCloudPrintURLFetcher() : new CloudPrintURLFetcher; } // static CloudPrintURLFetcherFactory* CloudPrintURLFetcher::factory() { return g_factory; } // static void CloudPrintURLFetcher::set_factory(CloudPrintURLFetcherFactory* factory) { g_factory = factory; } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcher::Delegate::HandleRawResponse( const net::URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const net::ResponseCookies& cookies, const std::string& data) { return CONTINUE_PROCESSING; } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcher::Delegate::HandleRawData( const net::URLFetcher* source, const GURL& url, const std::string& data) { return CONTINUE_PROCESSING; } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcher::Delegate::HandleJSONData( const net::URLFetcher* source, const GURL& url, base::DictionaryValue* json_data, bool succeeded) { return CONTINUE_PROCESSING; } CloudPrintURLFetcher::CloudPrintURLFetcher() : delegate_(NULL), num_retries_(0), type_(REQUEST_MAX) { } bool CloudPrintURLFetcher::IsSameRequest(const net::URLFetcher* source) { return (request_.get() == source); } void CloudPrintURLFetcher::StartGetRequest( RequestType type, const GURL& url, Delegate* delegate, int max_retries, const std::string& additional_headers) { StartRequestHelper(type, url, net::URLFetcher::GET, delegate, max_retries, std::string(), std::string(), additional_headers); } void CloudPrintURLFetcher::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) { StartRequestHelper(type, url, net::URLFetcher::POST, delegate, max_retries, post_data_mime_type, post_data, additional_headers); } void CloudPrintURLFetcher::OnURLFetchComplete( const net::URLFetcher* source) { VLOG(1) << "CP_PROXY: OnURLFetchComplete, url: " << source->GetURL() << ", response code: " << source->GetResponseCode(); // Make sure we stay alive through the body of this function. scoped_refptr keep_alive(this); std::string data; source->GetResponseAsString(&data); ReportRequestTime(type_, base::Time::Now() - start_time_); ReportDownloadSize(type_, data.size()); ResponseAction action = delegate_->HandleRawResponse( source, source->GetURL(), source->GetStatus(), source->GetResponseCode(), source->GetCookies(), data); // If we get auth error, notify delegate and check if it wants to proceed. if (action == CONTINUE_PROCESSING && source->GetResponseCode() == net::HTTP_FORBIDDEN) { action = delegate_->OnRequestAuthError(); } if (action == CONTINUE_PROCESSING) { // We need to retry on all network errors. if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) action = RETRY_REQUEST; else action = delegate_->HandleRawData(source, source->GetURL(), data); if (action == CONTINUE_PROCESSING) { // If the delegate is not interested in handling the raw response data, // we assume that a JSON response is expected. If we do not get a JSON // response, we will retry (to handle the case where we got redirected // to a non-cloudprint-server URL eg. for authentication). bool succeeded = false; scoped_ptr response_dict = ParseResponseJSON(data, &succeeded); if (response_dict) { action = delegate_->HandleJSONData(source, source->GetURL(), response_dict.get(), succeeded); } else { action = RETRY_REQUEST; } } } // Retry the request if needed. if (action == RETRY_REQUEST) { // Explicitly call ReceivedContentWasMalformed() to ensure the current // request gets counted as a failure for calculation of the back-off // period. If it was already a failure by status code, this call will // be ignored. request_->ReceivedContentWasMalformed(); // If we receive error code from the server "Media Type Not Supported", // there is no reason to retry, request will never succeed. // In that case we should call OnRequestGiveUp() right away. if (source->GetResponseCode() == net::HTTP_UNSUPPORTED_MEDIA_TYPE) num_retries_ = source->GetMaxRetriesOn5xx(); ++num_retries_; if ((-1 != source->GetMaxRetriesOn5xx()) && (num_retries_ > source->GetMaxRetriesOn5xx())) { // Retry limit reached. Give up. delegate_->OnRequestGiveUp(); action = STOP_PROCESSING; } else { // Either no retry limit specified or retry limit has not yet been // reached. Try again. Set up the request headers again because the token // may have changed. SetupRequestHeaders(); request_->SetRequestContext(GetRequestContextGetter()); start_time_ = base::Time::Now(); request_->Start(); } } if (action != RETRY_REQUEST) { ReportRetriesCount(type_, num_retries_); } } void CloudPrintURLFetcher::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) { DCHECK(delegate); type_ = type; UMA_HISTOGRAM_ENUMERATION("CloudPrint.UrlFetcherRequestType", type, REQUEST_MAX); // Persist the additional headers in case we need to retry the request. additional_headers_ = additional_headers; request_ = net::URLFetcher::Create(0, url, request_type, this); request_->SetRequestContext(GetRequestContextGetter()); // Since we implement our own retry logic, disable the retry in URLFetcher. request_->SetAutomaticallyRetryOn5xx(false); request_->SetMaxRetriesOn5xx(max_retries); delegate_ = delegate; SetupRequestHeaders(); request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES); if (request_type == net::URLFetcher::POST) { request_->SetUploadData(post_data_mime_type, post_data); ReportUploadSize(type_, post_data.size()); } start_time_ = base::Time::Now(); request_->Start(); } void CloudPrintURLFetcher::SetupRequestHeaders() { std::string headers = delegate_->GetAuthHeader(); if (!headers.empty()) headers += "\r\n"; headers += kChromeCloudPrintProxyHeader; if (!additional_headers_.empty()) { headers += "\r\n"; headers += additional_headers_; } request_->SetExtraRequestHeaders(headers); } CloudPrintURLFetcher::~CloudPrintURLFetcher() {} net::URLRequestContextGetter* CloudPrintURLFetcher::GetRequestContextGetter() { ServiceURLRequestContextGetter* getter = g_service_process->GetServiceURLRequestContextGetter(); // Now set up the user agent for cloudprint. std::string user_agent = getter->user_agent(); base::StringAppendF(&user_agent, " %s", kCloudPrintUserAgent); getter->set_user_agent(user_agent); return getter; } } // namespace cloud_print