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

Side by Side Diff: components/ntp_snippets/ntp_snippets_fetcher.cc

Issue 1922083004: Allow fetching personalized snippets from ChromeReader. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase-update and minor polish Created 4 years, 7 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
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/ntp_snippets/ntp_snippets_fetcher.h" 5 #include "components/ntp_snippets/ntp_snippets_fetcher.h"
6 6
7 #include <stdlib.h>
8
7 #include "base/files/file_path.h" 9 #include "base/files/file_path.h"
8 #include "base/files/file_util.h" 10 #include "base/files/file_util.h"
9 #include "base/metrics/sparse_histogram.h" 11 #include "base/metrics/sparse_histogram.h"
10 #include "base/path_service.h" 12 #include "base/path_service.h"
11 #include "base/strings/string_number_conversions.h" 13 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h" 14 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h" 15 #include "base/strings/stringprintf.h"
16 #include "components/ntp_snippets/ntp_snippets_constants.h"
17 #include "components/signin/core/browser/profile_oauth2_token_service.h"
18 #include "components/signin/core/browser/signin_manager.h"
19 #include "components/signin/core/browser/signin_tracker.h"
20 #include "components/variations/variations_associated_data.h"
14 #include "google_apis/google_api_keys.h" 21 #include "google_apis/google_api_keys.h"
15 #include "net/base/load_flags.h" 22 #include "net/base/load_flags.h"
16 #include "net/http/http_request_headers.h"
17 #include "net/http/http_response_headers.h" 23 #include "net/http/http_response_headers.h"
18 #include "net/http/http_status_code.h" 24 #include "net/http/http_status_code.h"
19 #include "net/url_request/url_fetcher.h" 25 #include "net/url_request/url_fetcher.h"
26 #include "third_party/icu/source/common/unicode/uloc.h"
27 #include "third_party/icu/source/common/unicode/utypes.h"
20 28
21 using net::URLFetcher; 29 using net::URLFetcher;
22 using net::URLRequestContextGetter; 30 using net::URLRequestContextGetter;
23 using net::HttpRequestHeaders; 31 using net::HttpRequestHeaders;
24 using net::URLRequestStatus; 32 using net::URLRequestStatus;
25 33
26 namespace ntp_snippets { 34 namespace ntp_snippets {
27 35
28 namespace { 36 namespace {
29 37
38 const char kApiScope[] = "https://www.googleapis.com/auth/webhistory";
39
30 const char kStatusMessageURLRequestErrorFormat[] = "URLRequestStatus error %d"; 40 const char kStatusMessageURLRequestErrorFormat[] = "URLRequestStatus error %d";
31 const char kStatusMessageHTTPErrorFormat[] = "HTTP error %d"; 41 const char kStatusMessageHTTPErrorFormat[] = "HTTP error %d";
32 42
33 const char kContentSnippetsServerFormat[] = 43 const char kContentSnippetsServer[] =
34 "https://chromereader-pa.googleapis.com/v1/fetch?key=%s"; 44 "https://chromereader-pa.googleapis.com/v1/fetch";
45 const char kContentSnippetsServerNonAuthorizedParamFormat[] = "?key=%s";
Marc Treib 2016/05/04 11:50:09 Put in the server URL also via StringPrintf, inste
jkrcal 2016/05/04 14:04:08 Done.
46 const char kAuthorizationRequestHeaderFormat[] = "Bearer %s";
35 47
36 const char kRequestParameterFormat[] = 48 // Variation parameter for the variant of fetching to use.
49 const char kVariantName[] = "variant";
Marc Treib 2016/05/04 11:50:10 IMO this is overly generic. At least make it "fetc
jkrcal 2016/05/04 14:04:08 Done.
50
51 // Constants listing possible values of the "variant" parameter.
52 const char kVariantRestricted[] = "restricted";
53 const char kVariantPersonalized[] = "personalized";
54 const char kVariantRestrictedPersonalized[] = "restricted_personalized";
55
56 // At the moment METADATA=ALL equals to requiring TITLE, SNIPPET, THUMBNAIL
57 // which is exactly what we require.
Marc Treib 2016/05/04 11:50:09 We listed title, snippet, thumbnail separately on
jkrcal 2016/05/04 14:04:08 Done.
58 const char kRequestParameterFormatNonAuth[] =
37 "{" 59 "{"
38 " \"response_detail_level\": \"STANDARD\"," 60 " \"response_detail_level\": \"STANDARD\","
39 " \"advanced_options\": {" 61 " \"advanced_options\": {"
40 " \"local_scoring_params\": {" 62 " \"local_scoring_params\": {"
41 " \"content_params\": {" 63 " \"content_params\": {"
42 " \"only_return_personalized_results\": false" 64 " \"only_return_personalized_results\": false"
43 " }," 65 " },"
44 " \"content_restricts\": {" 66 " \"content_restricts\": {"
45 " \"type\": \"METADATA\"," 67 " \"type\": \"METADATA\","
46 " \"value\": \"TITLE\"" 68 " \"value\": \"ALL\""
47 " },"
48 " \"content_restricts\": {"
49 " \"type\": \"METADATA\","
50 " \"value\": \"SNIPPET\""
51 " },"
52 " \"content_restricts\": {"
53 " \"type\": \"METADATA\","
54 " \"value\": \"THUMBNAIL\""
55 " }" 69 " }"
56 "%s" 70 "%s"
57 " }," 71 " },"
58 " \"global_scoring_params\": {" 72 " \"global_scoring_params\": {"
59 " \"num_to_return\": %i" 73 " \"num_to_return\": %i,"
74 " \"sort_type\": 1"
60 " }" 75 " }"
61 " }" 76 " }"
62 "}"; 77 "}";
78
79 const char kRequestParameterFormatAuth[] =
Marc Treib 2016/05/04 11:50:09 This is mostly the same as the NonAuth one. Can we
jkrcal 2016/05/04 14:04:08 Merged via printf. I am not sure json dict is wort
80 "{"
81 " \"response_detail_level\": \"STANDARD\","
82 " \"obfuscated_gaia_id\": \"%s\","
83 " \"advanced_options\": {"
84 " \"local_scoring_params\": {"
85 " \"content_params\": {"
86 " \"only_return_personalized_results\": false,"
87 " \"user_segment\": \"%s\""
88 " },"
89 " \"content_restricts\": {"
90 " \"type\": \"METADATA\","
91 " \"value\": \"ALL\""
92 " }"
93 "%s"
94 " },"
95 " \"global_scoring_params\": {"
96 " \"num_to_return\": %i,"
97 " \"sort_type\": 1"
98 " }"
99 " }"
100 "}";
63 101
64 const char kHostRestrictFormat[] = 102 const char kHostRestrictFormat[] =
65 " ,\"content_selectors\": {" 103 " ,\"content_selectors\": {"
66 " \"type\": \"HOST_RESTRICT\"," 104 " \"type\": \"HOST_RESTRICT\","
67 " \"value\": \"%s\"" 105 " \"value\": \"%s\""
68 " }"; 106 " }";
69 107
70 } // namespace 108 } // namespace
71 109
72 NTPSnippetsFetcher::NTPSnippetsFetcher( 110 NTPSnippetsFetcher::NTPSnippetsFetcher(
111 SigninManagerBase* signin_manager,
112 OAuth2TokenService* token_service,
73 scoped_refptr<URLRequestContextGetter> url_request_context_getter, 113 scoped_refptr<URLRequestContextGetter> url_request_context_getter,
74 bool is_stable_channel) 114 bool is_stable_channel)
75 : url_request_context_getter_(url_request_context_getter), 115 : OAuth2TokenService::Consumer("NTP_snippets"),
76 is_stable_channel_(is_stable_channel) {} 116 url_request_context_getter_(url_request_context_getter),
117 signin_manager_(signin_manager),
118 token_service_(token_service),
119 waiting_for_refresh_token_(false),
120 is_stable_channel_(is_stable_channel){
Bernhard Bauer 2016/05/04 11:46:58 Space before opening brace (also in other places i
jkrcal 2016/05/04 14:04:08 Done.
121 // Parse the variation parameters and set the defaults if missing.
122 variant_ = variations::GetVariationParamValue(ntp_snippets::kStudyName,
123 kVariantName);
124 if (variant_ == "")
Bernhard Bauer 2016/05/04 11:46:58 .empty()
Marc Treib 2016/05/04 11:50:10 variant_.empty() Also, what if it's an unknown st
jkrcal 2016/05/04 14:04:08 Done.
jkrcal 2016/05/04 14:04:08 Done.
125 variant_ = kVariantRestrictedPersonalized;
126 }
77 127
78 NTPSnippetsFetcher::~NTPSnippetsFetcher() {} 128 NTPSnippetsFetcher::~NTPSnippetsFetcher() {
129 if (waiting_for_refresh_token_)
130 token_service_->RemoveObserver(this);
131 }
79 132
80 std::unique_ptr<NTPSnippetsFetcher::SnippetsAvailableCallbackList::Subscription> 133 std::unique_ptr<NTPSnippetsFetcher::SnippetsAvailableCallbackList::Subscription>
81 NTPSnippetsFetcher::AddCallback(const SnippetsAvailableCallback& callback) { 134 NTPSnippetsFetcher::AddCallback(const SnippetsAvailableCallback& callback) {
82 return callback_list_.Add(callback); 135 return callback_list_.Add(callback);
83 } 136 }
84 137
85 void NTPSnippetsFetcher::FetchSnippets(const std::set<std::string>& hosts, 138 void NTPSnippetsFetcher::FetchSnippets(const std::set<std::string>& hosts,
139 const std::string& language_code,
86 int count) { 140 int count) {
87 const std::string& key = is_stable_channel_ 141 // TODO(treib): What to do if there's already a pending request?
Bernhard Bauer 2016/05/04 11:46:58 This does seem like a thing we should worry about.
Marc Treib 2016/05/04 11:50:09 Bad merge: This line had been removed (there's now
jkrcal 2016/05/04 14:04:08 Done.
jkrcal 2016/05/04 14:04:08 Bad merge: This line had been removed (there's now
88 ? google_apis::GetAPIKey() 142 hosts_ = hosts;
89 : google_apis::GetNonStableAPIKey(); 143
90 std::string url = 144 // Translate the BCP 47 |language_code| into a posix locale string
91 base::StringPrintf(kContentSnippetsServerFormat, key.c_str()); 145 char locale[20];
Bernhard Bauer 2016/05/04 11:46:58 Why 20?
Marc Treib 2016/05/04 11:50:10 Why 20?
jkrcal 2016/05/04 14:04:07 Done.
jkrcal 2016/05/04 14:04:08 Done.
146 UErrorCode error;
147 uloc_forLanguageTag(language_code.c_str(), locale, 20, NULL, &error);
Bernhard Bauer 2016/05/04 11:46:58 nullptr
Marc Treib 2016/05/04 11:50:10 s/NULL/nullptr/
jkrcal 2016/05/04 14:04:07 Done.
jkrcal 2016/05/04 14:04:08 Done.
148 DCHECK(error == U_ZERO_ERROR) <<
Bernhard Bauer 2016/05/04 11:46:58 Use DCHECK_EQ (with the expected value first) for
Marc Treib 2016/05/04 11:50:09 I don't think this warrants a DCHECK. (D)LOG(WARNI
jkrcal 2016/05/04 14:04:07 Thanks for the hint. In the end, I used DLOG(WARNI
jkrcal 2016/05/04 14:04:07 Done.
149 "Error in translating language code to a locale string: " << error;
150 locale_ = std::string(locale);
Bernhard Bauer 2016/05/04 11:46:58 The std::string constructor is implicit (or you co
jkrcal 2016/05/04 14:04:07 Done.
151
152 count_ = count;
153
154 if (UseAuthenticated()){
155 if (signin_manager_->IsAuthenticated()) {
156 // signed-in: get OAuth for the API --> fetch snippets
Marc Treib 2016/05/04 11:50:09 nit: Start with a captial letter and end with a pe
jkrcal 2016/05/04 14:04:08 Done.
157 StartTokenRequest();
158
Marc Treib 2016/05/04 11:50:09 no empty line
jkrcal 2016/05/04 14:04:07 Done.
159 } else if (signin_manager_->AuthInProgress()){
160 // currently signing in: wait for auth to finish (the refresh token) -->
161 // get OAuth for the API --> fetch snippets
162 if (!waiting_for_refresh_token_) {
163 // Wait until we get a refresh token.
164 waiting_for_refresh_token_ = true;
165 token_service_->AddObserver(this);
166 }
167 }
Marc Treib 2016/05/04 11:50:10 Hm, so the "else" here (not authenticated or auth
jkrcal 2016/05/04 14:04:07 This is the else of the "UseAuthenticated()"-if an
Marc Treib 2016/05/04 15:24:24 Sorry, I wasn't clear. I meant the missing "else"
jkrcal 2016/05/09 11:58:05 Done.
168 } else {
169 // not signed in: fetch snippets (without authentication)
170 FetchSnippetsNonAuthenticated();
171 }
172 }
173
174 void NTPSnippetsFetcher::FetchSnippetsImpl(const std::string& url,
Bernhard Bauer 2016/05/04 11:46:58 I would represent URLs as GURL as much as possible
jkrcal 2016/05/04 14:04:08 Done.
175 HttpRequestHeaders& headers,
176 const std::string& request) {
92 url_fetcher_ = URLFetcher::Create(GURL(url), URLFetcher::POST, this); 177 url_fetcher_ = URLFetcher::Create(GURL(url), URLFetcher::POST, this);
178
93 url_fetcher_->SetRequestContext(url_request_context_getter_.get()); 179 url_fetcher_->SetRequestContext(url_request_context_getter_.get());
94 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 180 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
95 net::LOAD_DO_NOT_SAVE_COOKIES); 181 net::LOAD_DO_NOT_SAVE_COOKIES);
96 HttpRequestHeaders headers; 182
97 headers.SetHeader("Content-Type", "application/json; charset=UTF-8"); 183 headers.SetHeader("Content-Type", "application/json; charset=UTF-8");
98 url_fetcher_->SetExtraRequestHeaders(headers.ToString()); 184 url_fetcher_->SetExtraRequestHeaders(headers.ToString());
99 std::string host_restricts; 185 url_fetcher_->SetUploadData("application/json", request);
100 for (const std::string& host : hosts)
101 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str());
102 url_fetcher_->SetUploadData("application/json",
103 base::StringPrintf(kRequestParameterFormat,
104 host_restricts.c_str(),
105 count));
106
107 // Fetchers are sometimes cancelled because a network change was detected. 186 // Fetchers are sometimes cancelled because a network change was detected.
108 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); 187 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
109 // Try to make fetching the files bit more robust even with poor connection. 188 // Try to make fetching the files bit more robust even with poor connection.
110 url_fetcher_->SetMaxRetriesOn5xx(3); 189 url_fetcher_->SetMaxRetriesOn5xx(3);
111 url_fetcher_->Start(); 190 url_fetcher_->Start();
112 } 191 }
113 192
193 std::string NTPSnippetsFetcher::GetHostsRestrict() const {
Marc Treib 2016/05/04 11:50:09 GetHostRestricts
jkrcal 2016/05/04 14:04:08 Done.
Marc Treib 2016/05/04 15:24:24 GetHostRestricts - host is singular, restricts is
jkrcal 2016/05/09 11:58:05 Done. (sorry for the oversight of mine!)
194 std::string host_restricts;
195 if (variant_ == kVariantRestricted ||
196 variant_ == kVariantRestrictedPersonalized) {
197 for (const std::string& host : hosts_)
198 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str());
199 }
200 return host_restricts;
201 }
202
203 bool NTPSnippetsFetcher::UseAuthenticated() {
204 return (variant_ == kVariantPersonalized ||
205 (variant_ == kVariantRestrictedPersonalized && !hosts_.empty())) &&
206 (signin_manager_->IsAuthenticated() || signin_manager_->AuthInProgress());
207 }
208
209 void NTPSnippetsFetcher::FetchSnippetsNonAuthenticated() {
210 // When not providing OAuth token, we need to pass the Google API key
211 const std::string& key = is_stable_channel_
212 ? google_apis::GetAPIKey()
213 : google_apis::GetNonStableAPIKey();
214 std::string url = std::string(kContentSnippetsServer) +
215 base::StringPrintf(kContentSnippetsServerNonAuthorizedParamFormat,
Bernhard Bauer 2016/05/04 11:46:58 You could extend the format to include a parameter
jkrcal 2016/05/04 14:04:07 Done.
216 key.c_str());
217 HttpRequestHeaders headers;
Bernhard Bauer 2016/05/04 11:46:58 You could just inline this.
jkrcal 2016/05/04 14:04:08 Done.
218
219 FetchSnippetsImpl(url, headers,
220 base::StringPrintf(kRequestParameterFormatNonAuth,
221 GetHostsRestrict().c_str(),
222 count_));
223 }
224
225 void NTPSnippetsFetcher::FetchSnippetsAuthenticated(
226 const std::string& account_id,
227 const std::string& oauth_access_token) {
228
Marc Treib 2016/05/04 11:50:10 no empty line
jkrcal 2016/05/04 14:04:08 Done.
229 HttpRequestHeaders headers;
230 headers.SetHeader("Authorization",
231 base::StringPrintf(kAuthorizationRequestHeaderFormat,
232 oauth_access_token.c_str()));
233
234 FetchSnippetsImpl(std::string(kContentSnippetsServer), headers,
Marc Treib 2016/05/04 11:50:09 I don't think you need the explicit conversion to
jkrcal 2016/05/04 14:04:08 Done.
235 base::StringPrintf(kRequestParameterFormatAuth,
236 account_id.c_str(),
237 locale_.c_str(),
238 GetHostsRestrict().c_str(),
239 count_));
240 }
241
242 void NTPSnippetsFetcher::StartTokenRequest() {
243 OAuth2TokenService::ScopeSet scopes;
244 scopes.insert(kApiScope);
245 oauth_request_ = token_service_->StartRequest(
246 signin_manager_->GetAuthenticatedAccountId(), scopes, this);
247 }
248
249 ////////////////////////////////////////////////////////////////////////////////
250 // OAuth2TokenService::Consumer overrides
251 void NTPSnippetsFetcher::OnGetTokenSuccess(
252 const OAuth2TokenService::Request* request,
253 const std::string& access_token,
254 const base::Time& expiration_time) {
255 oauth_request_.reset();
256
257 FetchSnippetsAuthenticated(request->GetAccountId(), access_token);
258 }
259
260 void NTPSnippetsFetcher::OnGetTokenFailure(
261 const OAuth2TokenService::Request* request,
262 const GoogleServiceAuthError& error) {
263 oauth_request_.reset();
264 DLOG(ERROR) << "Unable to get token: " << error.ToString();
Marc Treib 2016/05/04 11:50:10 Should we fall back to the non-authenticated case
jkrcal 2016/05/04 14:04:08 Done.
Marc Treib 2016/05/04 15:24:24 FWIW, this was an actual question - I'm not sure w
jkrcal 2016/05/09 11:58:05 I am not sure either. I think fallback to non-auth
265 }
266
267 ////////////////////////////////////////////////////////////////////////////////
268 // OAuth2TokenService::Observer overrides
269 void NTPSnippetsFetcher::OnRefreshTokenAvailable(
270 const std::string& account_id) {
271 token_service_->RemoveObserver(this);
272 waiting_for_refresh_token_ = false;
273 StartTokenRequest();
274 }
275
114 //////////////////////////////////////////////////////////////////////////////// 276 ////////////////////////////////////////////////////////////////////////////////
115 // URLFetcherDelegate overrides 277 // URLFetcherDelegate overrides
116 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) { 278 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) {
117 DCHECK_EQ(url_fetcher_.get(), source); 279 DCHECK_EQ(url_fetcher_.get(), source);
118 280
119 std::string message; 281 std::string message;
120 const URLRequestStatus& status = source->GetStatus(); 282 const URLRequestStatus& status = source->GetStatus();
121 283
122 UMA_HISTOGRAM_SPARSE_SLOWLY( 284 UMA_HISTOGRAM_SPARSE_SLOWLY(
123 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode", 285 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode",
124 status.is_success() ? source->GetResponseCode() : status.error()); 286 status.is_success() ? source->GetResponseCode() : status.error());
125 287
126 if (!status.is_success()) { 288 if (!status.is_success()) {
127 message = base::StringPrintf(kStatusMessageURLRequestErrorFormat, 289 message = base::StringPrintf(kStatusMessageURLRequestErrorFormat,
128 status.error()); 290 status.error());
129 } else if (source->GetResponseCode() != net::HTTP_OK) { 291 } else if (source->GetResponseCode() != net::HTTP_OK) {
Bernhard Bauer 2016/05/04 11:46:58 Now that we use authentication, we need to deal wi
jkrcal 2016/05/04 14:04:08 I added a TODO to solve this later (in a common cl
130 message = base::StringPrintf(kStatusMessageHTTPErrorFormat, 292 message = base::StringPrintf(kStatusMessageHTTPErrorFormat,
131 source->GetResponseCode()); 293 source->GetResponseCode());
132 } 294 }
133 295
134 std::string response; 296 std::string response;
135 if (!message.empty()) { 297 if (!message.empty()) {
298 std::string error_response;
299 source->GetResponseAsString(&error_response);
Marc Treib 2016/05/04 11:50:09 Now GetResponseAsString is called in both cases, s
jkrcal 2016/05/04 14:04:08 In the if branch I want to use a different variabl
Marc Treib 2016/05/04 15:24:24 Ah, I see. Okay, fair enough.
136 DLOG(WARNING) << message << " while trying to download " 300 DLOG(WARNING) << message << " while trying to download "
137 << source->GetURL().spec(); 301 << source->GetURL().spec() << ":"
Bernhard Bauer 2016/05/04 11:46:58 Super-nit: space after colon :)
jkrcal 2016/05/04 14:04:07 Done.
302 << error_response;
138 303
139 } else { 304 } else {
140 bool stores_result_to_string = source->GetResponseAsString(&response); 305 bool stores_result_to_string = source->GetResponseAsString(&response);
141 DCHECK(stores_result_to_string); 306 DCHECK(stores_result_to_string);
142 } 307 }
143 308
144 callback_list_.Notify(response, message); 309 callback_list_.Notify(response, message);
145 } 310 }
146 311
147 } // namespace ntp_snippets 312 } // namespace ntp_snippets
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698