Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(85)

Side by Side Diff: chrome/browser/search/suggestions/suggestions_service.cc

Issue 410673002: [Suggestions] Moving suggestions code to a new component (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix deps Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 #include "chrome/browser/search/suggestions/suggestions_service.h"
6
7 #include <sstream>
8 #include <string>
9
10 #include "base/memory/scoped_ptr.h"
11 #include "base/metrics/histogram.h"
12 #include "base/metrics/sparse_histogram.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/time/time.h"
16 #include "chrome/browser/search/suggestions/blacklist_store.h"
17 #include "chrome/browser/search/suggestions/suggestions_store.h"
18 #include "components/pref_registry/pref_registry_syncable.h"
19 #include "components/variations/variations_associated_data.h"
20 #include "components/variations/variations_http_header_provider.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "net/base/escape.h"
23 #include "net/base/load_flags.h"
24 #include "net/base/net_errors.h"
25 #include "net/base/url_util.h"
26 #include "net/http/http_response_headers.h"
27 #include "net/http/http_status_code.h"
28 #include "net/http/http_util.h"
29 #include "net/url_request/url_fetcher.h"
30 #include "net/url_request/url_request_status.h"
31 #include "url/gurl.h"
32
33 using base::CancelableClosure;
34 using content::BrowserThread;
35
36 namespace suggestions {
37
38 namespace {
39
40 // Used to UMA log the state of the last response from the server.
41 enum SuggestionsResponseState {
42 RESPONSE_EMPTY,
43 RESPONSE_INVALID,
44 RESPONSE_VALID,
45 RESPONSE_STATE_SIZE
46 };
47
48 // Will log the supplied response |state|.
49 void LogResponseState(SuggestionsResponseState state) {
50 UMA_HISTOGRAM_ENUMERATION("Suggestions.ResponseState", state,
51 RESPONSE_STATE_SIZE);
52 }
53
54 // Obtains the experiment parameter under the supplied |key|, or empty string
55 // if the parameter does not exist.
56 std::string GetExperimentParam(const std::string& key) {
57 return chrome_variations::GetVariationParamValue(kSuggestionsFieldTrialName,
58 key);
59 }
60
61 GURL BuildBlacklistRequestURL(const std::string& blacklist_url_prefix,
62 const GURL& candidate_url) {
63 return GURL(blacklist_url_prefix +
64 net::EscapeQueryParamValue(candidate_url.spec(), true));
65 }
66
67 // Runs each callback in |requestors| on |suggestions|, then deallocates
68 // |requestors|.
69 void DispatchRequestsAndClear(
70 const SuggestionsProfile& suggestions,
71 std::vector<SuggestionsService::ResponseCallback>* requestors) {
72 std::vector<SuggestionsService::ResponseCallback>::iterator it;
73 for (it = requestors->begin(); it != requestors->end(); ++it) {
74 if (!it->is_null()) it->Run(suggestions);
75 }
76 std::vector<SuggestionsService::ResponseCallback>().swap(*requestors);
77 }
78
79 const int kDefaultRequestTimeoutMs = 200;
80
81 // Default delay used when scheduling a blacklist request.
82 const int kBlacklistDefaultDelaySec = 1;
83
84 // Multiplier on the delay used when scheduling a blacklist request, in case the
85 // last observed request was unsuccessful.
86 const int kBlacklistBackoffMultiplier = 2;
87
88 // Maximum valid delay for scheduling a request. Candidate delays larger than
89 // this are rejected. This means the maximum backoff is at least 300 / 2, i.e.
90 // 2.5 minutes.
91 const int kBlacklistMaxDelaySec = 300; // 5 minutes
92
93 } // namespace
94
95 const char kSuggestionsFieldTrialName[] = "ChromeSuggestions";
96 const char kSuggestionsFieldTrialURLParam[] = "url";
97 const char kSuggestionsFieldTrialCommonParamsParam[] = "common_params";
98 const char kSuggestionsFieldTrialBlacklistPathParam[] = "blacklist_path";
99 const char kSuggestionsFieldTrialBlacklistUrlParam[] = "blacklist_url_param";
100 const char kSuggestionsFieldTrialStateParam[] = "state";
101 const char kSuggestionsFieldTrialControlParam[] = "control";
102 const char kSuggestionsFieldTrialStateEnabled[] = "enabled";
103 const char kSuggestionsFieldTrialTimeoutMs[] = "timeout_ms";
104
105 namespace {
106
107 std::string GetBlacklistUrlPrefix() {
108 std::stringstream blacklist_url_prefix_stream;
109 blacklist_url_prefix_stream
110 << GetExperimentParam(kSuggestionsFieldTrialURLParam)
111 << GetExperimentParam(kSuggestionsFieldTrialBlacklistPathParam) << "?"
112 << GetExperimentParam(kSuggestionsFieldTrialCommonParamsParam) << "&"
113 << GetExperimentParam(kSuggestionsFieldTrialBlacklistUrlParam) << "=";
114 return blacklist_url_prefix_stream.str();
115 }
116
117 } // namespace
118
119 SuggestionsService::SuggestionsService(
120 net::URLRequestContextGetter* url_request_context,
121 scoped_ptr<SuggestionsStore> suggestions_store,
122 scoped_ptr<ImageManager> thumbnail_manager,
123 scoped_ptr<BlacklistStore> blacklist_store)
124 : suggestions_store_(suggestions_store.Pass()),
125 blacklist_store_(blacklist_store.Pass()),
126 thumbnail_manager_(thumbnail_manager.Pass()),
127 url_request_context_(url_request_context),
128 blacklist_delay_sec_(kBlacklistDefaultDelaySec),
129 weak_ptr_factory_(this),
130 request_timeout_ms_(kDefaultRequestTimeoutMs) {
131 // Obtain various parameters from Variations.
132 suggestions_url_ =
133 GURL(GetExperimentParam(kSuggestionsFieldTrialURLParam) + "?" +
134 GetExperimentParam(kSuggestionsFieldTrialCommonParamsParam));
135 blacklist_url_prefix_ = GetBlacklistUrlPrefix();
136 std::string timeout = GetExperimentParam(kSuggestionsFieldTrialTimeoutMs);
137 int temp_timeout;
138 if (!timeout.empty() && base::StringToInt(timeout, &temp_timeout)) {
139 request_timeout_ms_ = temp_timeout;
140 }
141 }
142
143 SuggestionsService::~SuggestionsService() {}
144
145 // static
146 bool SuggestionsService::IsEnabled() {
147 return GetExperimentParam(kSuggestionsFieldTrialStateParam) ==
148 kSuggestionsFieldTrialStateEnabled;
149 }
150
151 // static
152 bool SuggestionsService::IsControlGroup() {
153 return GetExperimentParam(kSuggestionsFieldTrialControlParam) ==
154 kSuggestionsFieldTrialStateEnabled;
155 }
156
157 void SuggestionsService::FetchSuggestionsData(
158 SuggestionsService::ResponseCallback callback) {
159 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
160
161 FetchSuggestionsDataNoTimeout(callback);
162
163 // Post a task to serve the cached suggestions if the request hasn't completed
164 // after some time. Cancels the previous such task, if one existed.
165 pending_timeout_closure_.reset(new CancelableClosure(base::Bind(
166 &SuggestionsService::OnRequestTimeout, weak_ptr_factory_.GetWeakPtr())));
167 BrowserThread::PostDelayedTask(
168 BrowserThread::UI, FROM_HERE, pending_timeout_closure_->callback(),
169 base::TimeDelta::FromMilliseconds(request_timeout_ms_));
170 }
171
172 void SuggestionsService::FetchSuggestionsDataNoTimeout(
173 SuggestionsService::ResponseCallback callback) {
174 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
175 if (pending_request_.get()) {
176 // Request already exists, so just add requestor to queue.
177 waiting_requestors_.push_back(callback);
178 return;
179 }
180
181 // Form new request.
182 DCHECK(waiting_requestors_.empty());
183 waiting_requestors_.push_back(callback);
184 IssueRequest(suggestions_url_);
185 }
186
187 void SuggestionsService::GetPageThumbnail(
188 const GURL& url,
189 base::Callback<void(const GURL&, const SkBitmap*)> callback) {
190 thumbnail_manager_->GetImageForURL(url, callback);
191 }
192
193 void SuggestionsService::BlacklistURL(
194 const GURL& candidate_url,
195 const SuggestionsService::ResponseCallback& callback) {
196 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
197 waiting_requestors_.push_back(callback);
198
199 // Blacklist locally, for immediate effect.
200 if (!blacklist_store_->BlacklistUrl(candidate_url)) {
201 DVLOG(1) << "Failed blacklisting attempt.";
202 return;
203 }
204
205 // If there's an ongoing request, let it complete.
206 if (pending_request_.get()) return;
207
208 IssueRequest(BuildBlacklistRequestURL(blacklist_url_prefix_, candidate_url));
209 }
210
211 // static
212 bool SuggestionsService::GetBlacklistedUrl(const net::URLFetcher& request,
213 GURL* url) {
214 bool is_blacklist_request = StartsWithASCII(request.GetOriginalURL().spec(),
215 GetBlacklistUrlPrefix(), true);
216 if (!is_blacklist_request) return false;
217
218 // Extract the blacklisted URL from the blacklist request.
219 std::string blacklisted;
220 if (!net::GetValueForKeyInQuery(
221 request.GetOriginalURL(),
222 GetExperimentParam(kSuggestionsFieldTrialBlacklistUrlParam),
223 &blacklisted))
224 return false;
225
226 GURL blacklisted_url(blacklisted);
227 blacklisted_url.Swap(url);
228 return true;
229 }
230
231 // static
232 void SuggestionsService::RegisterProfilePrefs(
233 user_prefs::PrefRegistrySyncable* registry) {
234 SuggestionsStore::RegisterProfilePrefs(registry);
235 BlacklistStore::RegisterProfilePrefs(registry);
236 }
237
238 void SuggestionsService::IssueRequest(const GURL& url) {
239 pending_request_.reset(CreateSuggestionsRequest(url));
240 pending_request_->Start();
241 last_request_started_time_ = base::TimeTicks::Now();
242 }
243
244 net::URLFetcher* SuggestionsService::CreateSuggestionsRequest(const GURL& url) {
245 net::URLFetcher* request =
246 net::URLFetcher::Create(0, url, net::URLFetcher::GET, this);
247 request->SetLoadFlags(net::LOAD_DISABLE_CACHE);
248 request->SetRequestContext(url_request_context_);
249 // Add Chrome experiment state to the request headers.
250 net::HttpRequestHeaders headers;
251 variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
252 request->GetOriginalURL(), false, false, &headers);
253 request->SetExtraRequestHeaders(headers.ToString());
254 return request;
255 }
256
257 void SuggestionsService::OnRequestTimeout() {
258 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
259 ServeFromCache();
260 }
261
262 void SuggestionsService::OnURLFetchComplete(const net::URLFetcher* source) {
263 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
264 DCHECK_EQ(pending_request_.get(), source);
265
266 // We no longer need the timeout closure. Delete it whether or not it has run.
267 // If it hasn't, this cancels it.
268 pending_timeout_closure_.reset();
269
270 // The fetcher will be deleted when the request is handled.
271 scoped_ptr<const net::URLFetcher> request(pending_request_.release());
272 const net::URLRequestStatus& request_status = request->GetStatus();
273 if (request_status.status() != net::URLRequestStatus::SUCCESS) {
274 UMA_HISTOGRAM_SPARSE_SLOWLY("Suggestions.FailedRequestErrorCode",
275 -request_status.error());
276 DVLOG(1) << "Suggestions server request failed with error: "
277 << request_status.error() << ": "
278 << net::ErrorToString(request_status.error());
279 // Dispatch the cached profile on error.
280 ServeFromCache();
281 ScheduleBlacklistUpload(false);
282 return;
283 }
284
285 // Log the response code.
286 const int response_code = request->GetResponseCode();
287 UMA_HISTOGRAM_SPARSE_SLOWLY("Suggestions.FetchResponseCode", response_code);
288 if (response_code != net::HTTP_OK) {
289 // Aggressively clear the store.
290 suggestions_store_->ClearSuggestions();
291 DispatchRequestsAndClear(SuggestionsProfile(), &waiting_requestors_);
292 ScheduleBlacklistUpload(false);
293 return;
294 }
295
296 const base::TimeDelta latency =
297 base::TimeTicks::Now() - last_request_started_time_;
298 UMA_HISTOGRAM_MEDIUM_TIMES("Suggestions.FetchSuccessLatency", latency);
299
300 // Handle a successful blacklisting.
301 GURL blacklisted_url;
302 if (GetBlacklistedUrl(*source, &blacklisted_url)) {
303 blacklist_store_->RemoveUrl(blacklisted_url);
304 }
305
306 std::string suggestions_data;
307 bool success = request->GetResponseAsString(&suggestions_data);
308 DCHECK(success);
309
310 // Compute suggestions, and dispatch then to requestors. On error still
311 // dispatch empty suggestions.
312 SuggestionsProfile suggestions;
313 if (suggestions_data.empty()) {
314 LogResponseState(RESPONSE_EMPTY);
315 suggestions_store_->ClearSuggestions();
316 } else if (suggestions.ParseFromString(suggestions_data)) {
317 LogResponseState(RESPONSE_VALID);
318 thumbnail_manager_->Initialize(suggestions);
319 suggestions_store_->StoreSuggestions(suggestions);
320 } else {
321 LogResponseState(RESPONSE_INVALID);
322 suggestions_store_->LoadSuggestions(&suggestions);
323 }
324
325 FilterAndServe(&suggestions);
326 ScheduleBlacklistUpload(true);
327 }
328
329 void SuggestionsService::Shutdown() {
330 // Cancel pending request and timeout closure, then serve existing requestors
331 // from cache.
332 pending_request_.reset(NULL);
333 pending_timeout_closure_.reset(NULL);
334 ServeFromCache();
335 }
336
337 void SuggestionsService::ServeFromCache() {
338 SuggestionsProfile suggestions;
339 suggestions_store_->LoadSuggestions(&suggestions);
340 FilterAndServe(&suggestions);
341 }
342
343 void SuggestionsService::FilterAndServe(SuggestionsProfile* suggestions) {
344 blacklist_store_->FilterSuggestions(suggestions);
345 DispatchRequestsAndClear(*suggestions, &waiting_requestors_);
346 }
347
348 void SuggestionsService::ScheduleBlacklistUpload(bool last_request_successful) {
349 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
350
351 UpdateBlacklistDelay(last_request_successful);
352
353 // Schedule a blacklist upload task.
354 GURL blacklist_url;
355 if (blacklist_store_->GetFirstUrlFromBlacklist(&blacklist_url)) {
356 base::Closure blacklist_cb =
357 base::Bind(&SuggestionsService::UploadOneFromBlacklist,
358 weak_ptr_factory_.GetWeakPtr());
359 BrowserThread::PostDelayedTask(
360 BrowserThread::UI, FROM_HERE, blacklist_cb,
361 base::TimeDelta::FromSeconds(blacklist_delay_sec_));
362 }
363 }
364
365 void SuggestionsService::UploadOneFromBlacklist() {
366 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
367
368 // If there's an ongoing request, let it complete.
369 if (pending_request_.get()) return;
370
371 GURL blacklist_url;
372 if (!blacklist_store_->GetFirstUrlFromBlacklist(&blacklist_url))
373 return; // Local blacklist is empty.
374
375 // Send blacklisting request.
376 IssueRequest(BuildBlacklistRequestURL(blacklist_url_prefix_, blacklist_url));
377 }
378
379 void SuggestionsService::UpdateBlacklistDelay(bool last_request_successful) {
380 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
381
382 if (last_request_successful) {
383 blacklist_delay_sec_ = kBlacklistDefaultDelaySec;
384 } else {
385 int candidate_delay = blacklist_delay_sec_ * kBlacklistBackoffMultiplier;
386 if (candidate_delay < kBlacklistMaxDelaySec)
387 blacklist_delay_sec_ = candidate_delay;
388 }
389 }
390
391 } // namespace suggestions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698