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/browser/net/http_pipelining_compatibility_client.h"
6
7 #include "base/metrics/field_trial.h"
8 #include "base/metrics/histogram.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/stringprintf.h"
12 #include "chrome/browser/io_thread.h"
13 #include "chrome/common/chrome_version_info.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "net/base/load_flags.h"
16 #include "net/base/network_change_notifier.h"
17 #include "net/base/request_priority.h"
18 #include "net/disk_cache/histogram_macros.h"
19 #include "net/http/http_network_layer.h"
20 #include "net/http/http_network_session.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/http/http_version.h"
23 #include "net/proxy/proxy_config.h"
24 #include "net/proxy/proxy_service.h"
25 #include "net/url_request/url_request_context.h"
26 #include "net/url_request/url_request_context_getter.h"
27
28 namespace chrome_browser_net {
29
30 static const int kCanaryRequestId = 999;
31
32 namespace {
33
34 // There is one Request per RequestInfo passed in to Start() above.
35 class Request : public internal::PipelineTestRequest,
36 public net::URLRequest::Delegate {
37 public:
38 Request(int request_id,
39 const std::string& base_url,
40 const RequestInfo& info,
41 internal::PipelineTestRequest::Delegate* delegate,
42 net::URLRequestContext* url_request_context);
43
~Request()44 virtual ~Request() {}
45
46 virtual void Start() OVERRIDE;
47
48 protected:
49 // Called when this request has determined its result. Returns the result to
50 // the |client_|.
51 virtual void Finished(internal::PipelineTestRequest::Status result);
52
response() const53 const std::string& response() const { return response_; }
54
delegate()55 internal::PipelineTestRequest::Delegate* delegate() { return delegate_; }
56
57 private:
58 // Called when a response can be read. Reads bytes into |response_| until it
59 // consumes the entire response or it encounters an error.
60 void DoRead();
61
62 // Called when all bytes have been received. Compares the |response_| to
63 // |info_|'s expected response.
64 virtual void DoReadFinished();
65
66 // net::URLRequest::Delegate interface
67 virtual void OnReceivedRedirect(net::URLRequest* request,
68 const GURL& new_url,
69 bool* defer_redirect) OVERRIDE;
70 virtual void OnSSLCertificateError(net::URLRequest* request,
71 const net::SSLInfo& ssl_info,
72 bool fatal) OVERRIDE;
73 virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE;
74 virtual void OnReadCompleted(net::URLRequest* request,
75 int bytes_read) OVERRIDE;
76
77 internal::PipelineTestRequest::Delegate* delegate_;
78 const int request_id_;
79 scoped_ptr<net::URLRequest> url_request_;
80 const RequestInfo info_;
81 scoped_refptr<net::IOBuffer> read_buffer_;
82 std::string response_;
83 int response_code_;
84 };
85
Request(int request_id,const std::string & base_url,const RequestInfo & info,internal::PipelineTestRequest::Delegate * delegate,net::URLRequestContext * url_request_context)86 Request::Request(int request_id,
87 const std::string& base_url,
88 const RequestInfo& info,
89 internal::PipelineTestRequest::Delegate* delegate,
90 net::URLRequestContext* url_request_context)
91 : delegate_(delegate),
92 request_id_(request_id),
93 url_request_(url_request_context->CreateRequest(GURL(base_url +
94 info.filename),
95 net::DEFAULT_PRIORITY,
96 this)),
97 info_(info),
98 response_code_(0) {
99 url_request_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
100 net::LOAD_DISABLE_CACHE |
101 net::LOAD_DO_NOT_SAVE_COOKIES |
102 net::LOAD_DO_NOT_SEND_COOKIES |
103 net::LOAD_DO_NOT_PROMPT_FOR_LOGIN |
104 net::LOAD_DO_NOT_SEND_AUTH_DATA);
105 }
106
Start()107 void Request::Start() {
108 url_request_->Start();
109 }
110
OnReceivedRedirect(net::URLRequest * request,const GURL & new_url,bool * defer_redirect)111 void Request::OnReceivedRedirect(
112 net::URLRequest* request,
113 const GURL& new_url,
114 bool* defer_redirect) {
115 *defer_redirect = true;
116 request->Cancel();
117 Finished(STATUS_REDIRECTED);
118 }
119
OnSSLCertificateError(net::URLRequest * request,const net::SSLInfo & ssl_info,bool fatal)120 void Request::OnSSLCertificateError(
121 net::URLRequest* request,
122 const net::SSLInfo& ssl_info,
123 bool fatal) {
124 Finished(STATUS_CERT_ERROR);
125 }
126
OnResponseStarted(net::URLRequest * request)127 void Request::OnResponseStarted(net::URLRequest* request) {
128 response_code_ = request->GetResponseCode();
129 if (response_code_ != 200) {
130 Finished(STATUS_BAD_RESPONSE_CODE);
131 return;
132 }
133 const net::HttpVersion required_version(1, 1);
134 if (request->response_info().headers->GetParsedHttpVersion() <
135 required_version) {
136 Finished(STATUS_BAD_HTTP_VERSION);
137 return;
138 }
139 read_buffer_ = new net::IOBuffer(info_.expected_response.length());
140 DoRead();
141 }
142
OnReadCompleted(net::URLRequest * request,int bytes_read)143 void Request::OnReadCompleted(net::URLRequest* request, int bytes_read) {
144 if (bytes_read == 0) {
145 DoReadFinished();
146 } else if (bytes_read < 0) {
147 Finished(STATUS_NETWORK_ERROR);
148 } else {
149 response_.append(read_buffer_->data(), bytes_read);
150 if (response_.length() <= info_.expected_response.length()) {
151 DoRead();
152 } else if (response_.find(info_.expected_response) == 0) {
153 Finished(STATUS_TOO_LARGE);
154 } else {
155 Finished(STATUS_CONTENT_MISMATCH);
156 }
157 }
158 }
159
DoRead()160 void Request::DoRead() {
161 int bytes_read = 0;
162 if (url_request_->Read(read_buffer_.get(), info_.expected_response.length(),
163 &bytes_read)) {
164 OnReadCompleted(url_request_.get(), bytes_read);
165 }
166 }
167
DoReadFinished()168 void Request::DoReadFinished() {
169 if (response_.length() != info_.expected_response.length()) {
170 if (info_.expected_response.find(response_) == 0) {
171 Finished(STATUS_TOO_SMALL);
172 } else {
173 Finished(STATUS_CONTENT_MISMATCH);
174 }
175 } else if (response_ == info_.expected_response) {
176 Finished(STATUS_SUCCESS);
177 } else {
178 Finished(STATUS_CONTENT_MISMATCH);
179 }
180 }
181
Finished(internal::PipelineTestRequest::Status result)182 void Request::Finished(internal::PipelineTestRequest::Status result) {
183 const net::URLRequestStatus status = url_request_->status();
184 url_request_.reset();
185 if (response_code_ > 0) {
186 delegate()->ReportResponseCode(request_id_, response_code_);
187 }
188 if (status.status() == net::URLRequestStatus::FAILED) {
189 // Network errors trump all other status codes, because network errors can
190 // be detected by the network stack even with real content. If we determine
191 // that all pipelining errors can be detected by the network stack, then we
192 // don't need to worry about broken proxies.
193 delegate()->ReportNetworkError(request_id_, status.error());
194 delegate()->OnRequestFinished(request_id_, STATUS_NETWORK_ERROR);
195 } else {
196 delegate()->OnRequestFinished(request_id_, result);
197 }
198 // WARNING: We may be deleted at this point.
199 }
200
201 // A special non-pipelined request sent before pipelining begins to test basic
202 // HTTP connectivity.
203 class CanaryRequest : public Request {
204 public:
CanaryRequest(int request_id,const std::string & base_url,const RequestInfo & info,internal::PipelineTestRequest::Delegate * delegate,net::URLRequestContext * url_request_context)205 CanaryRequest(int request_id,
206 const std::string& base_url,
207 const RequestInfo& info,
208 internal::PipelineTestRequest::Delegate* delegate,
209 net::URLRequestContext* url_request_context)
210 : Request(request_id, base_url, info, delegate, url_request_context) {
211 }
212
~CanaryRequest()213 virtual ~CanaryRequest() {}
214
215 private:
Finished(internal::PipelineTestRequest::Status result)216 virtual void Finished(
217 internal::PipelineTestRequest::Status result) OVERRIDE {
218 delegate()->OnCanaryFinished(result);
219 }
220 };
221
222 // A special request that parses a /stats.txt response from the test server.
223 class StatsRequest : public Request {
224 public:
225 // Note that |info.expected_response| is only used to determine the correct
226 // length of the response. The exact string content isn't used.
StatsRequest(int request_id,const std::string & base_url,const RequestInfo & info,internal::PipelineTestRequest::Delegate * delegate,net::URLRequestContext * url_request_context)227 StatsRequest(int request_id,
228 const std::string& base_url,
229 const RequestInfo& info,
230 internal::PipelineTestRequest::Delegate* delegate,
231 net::URLRequestContext* url_request_context)
232 : Request(request_id, base_url, info, delegate, url_request_context) {
233 }
234
~StatsRequest()235 virtual ~StatsRequest() {}
236
237 private:
DoReadFinished()238 virtual void DoReadFinished() OVERRIDE {
239 internal::PipelineTestRequest::Status status =
240 internal::ProcessStatsResponse(response());
241 Finished(status);
242 }
243 };
244
245 class RequestFactory : public internal::PipelineTestRequest::Factory {
246 public:
NewRequest(int request_id,const std::string & base_url,const RequestInfo & info,internal::PipelineTestRequest::Delegate * delegate,net::URLRequestContext * url_request_context,internal::PipelineTestRequest::Type request_type)247 virtual internal::PipelineTestRequest* NewRequest(
248 int request_id,
249 const std::string& base_url,
250 const RequestInfo& info,
251 internal::PipelineTestRequest::Delegate* delegate,
252 net::URLRequestContext* url_request_context,
253 internal::PipelineTestRequest::Type request_type) OVERRIDE {
254 switch (request_type) {
255 case internal::PipelineTestRequest::TYPE_PIPELINED:
256 return new Request(request_id, base_url, info, delegate,
257 url_request_context);
258
259 case internal::PipelineTestRequest::TYPE_CANARY:
260 return new CanaryRequest(request_id, base_url, info, delegate,
261 url_request_context);
262
263 case internal::PipelineTestRequest::TYPE_STATS:
264 return new StatsRequest(request_id, base_url, info, delegate,
265 url_request_context);
266
267 default:
268 NOTREACHED();
269 return NULL;
270 }
271 }
272 };
273
274 } // anonymous namespace
275
HttpPipeliningCompatibilityClient(internal::PipelineTestRequest::Factory * factory)276 HttpPipeliningCompatibilityClient::HttpPipeliningCompatibilityClient(
277 internal::PipelineTestRequest::Factory* factory)
278 : factory_(factory),
279 num_finished_(0),
280 num_succeeded_(0) {
281 if (!factory_.get()) {
282 factory_.reset(new RequestFactory);
283 }
284 }
285
~HttpPipeliningCompatibilityClient()286 HttpPipeliningCompatibilityClient::~HttpPipeliningCompatibilityClient() {
287 }
288
Start(const std::string & base_url,std::vector<RequestInfo> & requests,Options options,const net::CompletionCallback & callback,net::URLRequestContext * url_request_context)289 void HttpPipeliningCompatibilityClient::Start(
290 const std::string& base_url,
291 std::vector<RequestInfo>& requests,
292 Options options,
293 const net::CompletionCallback& callback,
294 net::URLRequestContext* url_request_context) {
295 net::HttpNetworkSession* old_session =
296 url_request_context->http_transaction_factory()->GetSession();
297 net::HttpNetworkSession::Params params = old_session->params();
298 params.force_http_pipelining = true;
299 scoped_refptr<net::HttpNetworkSession> session =
300 new net::HttpNetworkSession(params);
301 http_transaction_factory_.reset(
302 net::HttpNetworkLayer::CreateFactory(session.get()));
303
304 url_request_context_.reset(new net::URLRequestContext);
305 url_request_context_->CopyFrom(url_request_context);
306 url_request_context_->set_http_transaction_factory(
307 http_transaction_factory_.get());
308
309 finished_callback_ = callback;
310 for (size_t i = 0; i < requests.size(); ++i) {
311 requests_.push_back(factory_->NewRequest(
312 i, base_url, requests[i], this, url_request_context_.get(),
313 internal::PipelineTestRequest::TYPE_PIPELINED));
314 }
315 if (options == PIPE_TEST_COLLECT_SERVER_STATS ||
316 options == PIPE_TEST_CANARY_AND_STATS) {
317 RequestInfo info;
318 info.filename = "stats.txt";
319 // This is just to determine the expected length of the response.
320 // StatsRequest doesn't expect this exact value, but it does expect this
321 // exact length.
322 info.expected_response =
323 "were_all_requests_http_1_1:1,max_pipeline_depth:5";
324 requests_.push_back(factory_->NewRequest(
325 requests.size(), base_url, info, this, url_request_context_.get(),
326 internal::PipelineTestRequest::TYPE_STATS));
327 }
328 if (options == PIPE_TEST_RUN_CANARY_REQUEST ||
329 options == PIPE_TEST_CANARY_AND_STATS) {
330 RequestInfo info;
331 info.filename = "index.html";
332 info.expected_response =
333 "\nThis is a test server operated by Google. It's used by Google "
334 "Chrome to test\nproxies for compatibility with HTTP pipelining. More "
335 "information can be found\nhere:\n\nhttp://dev.chromium.org/developers/"
336 "design-documents/network-stack/http-pipelining\n\nSource code can be "
337 "found here:\n\nhttp://code.google.com/p/http-pipelining-test/\n";
338 canary_request_.reset(factory_->NewRequest(
339 kCanaryRequestId, base_url, info, this, url_request_context,
340 internal::PipelineTestRequest::TYPE_CANARY));
341 canary_request_->Start();
342 } else {
343 StartTestRequests();
344 }
345 }
346
StartTestRequests()347 void HttpPipeliningCompatibilityClient::StartTestRequests() {
348 for (size_t i = 0; i < requests_.size(); ++i) {
349 requests_[i]->Start();
350 }
351 }
352
OnCanaryFinished(internal::PipelineTestRequest::Status status)353 void HttpPipeliningCompatibilityClient::OnCanaryFinished(
354 internal::PipelineTestRequest::Status status) {
355 canary_request_.reset();
356 bool success = (status == internal::PipelineTestRequest::STATUS_SUCCESS);
357 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.CanarySuccess", success);
358 if (success) {
359 StartTestRequests();
360 } else {
361 finished_callback_.Run(0);
362 }
363 }
364
OnRequestFinished(int request_id,internal::PipelineTestRequest::Status status)365 void HttpPipeliningCompatibilityClient::OnRequestFinished(
366 int request_id, internal::PipelineTestRequest::Status status) {
367 // The CACHE_HISTOGRAM_* macros are used, because they allow dynamic metric
368 // names.
369 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "Status"),
370 status,
371 internal::PipelineTestRequest::STATUS_MAX);
372
373 ++num_finished_;
374 if (status == internal::PipelineTestRequest::STATUS_SUCCESS) {
375 ++num_succeeded_;
376 }
377 if (num_finished_ == requests_.size()) {
378 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.Success",
379 num_succeeded_ == requests_.size());
380 finished_callback_.Run(0);
381 }
382 }
383
ReportNetworkError(int request_id,int error_code)384 void HttpPipeliningCompatibilityClient::ReportNetworkError(int request_id,
385 int error_code) {
386 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "NetworkError"),
387 -error_code, 900);
388 }
389
ReportResponseCode(int request_id,int response_code)390 void HttpPipeliningCompatibilityClient::ReportResponseCode(int request_id,
391 int response_code) {
392 CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "ResponseCode"),
393 response_code, 600);
394 }
395
GetMetricName(int request_id,const char * description)396 std::string HttpPipeliningCompatibilityClient::GetMetricName(
397 int request_id, const char* description) {
398 return base::StringPrintf("NetConnectivity.Pipeline.%d.%s",
399 request_id, description);
400 }
401
402 namespace internal {
403
ProcessStatsResponse(const std::string & response)404 internal::PipelineTestRequest::Status ProcessStatsResponse(
405 const std::string& response) {
406 bool were_all_requests_http_1_1 = false;
407 int max_pipeline_depth = 0;
408
409 std::vector<std::pair<std::string, std::string> > kv_pairs;
410 base::SplitStringIntoKeyValuePairs(response, ':', ',', &kv_pairs);
411
412 if (kv_pairs.size() != 2) {
413 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
414 }
415
416 for (size_t i = 0; i < kv_pairs.size(); ++i) {
417 const std::string& key = kv_pairs[i].first;
418 int value;
419 if (!base::StringToInt(kv_pairs[i].second, &value)) {
420 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
421 }
422
423 if (key == "were_all_requests_http_1_1") {
424 were_all_requests_http_1_1 = (value == 1);
425 } else if (key == "max_pipeline_depth") {
426 max_pipeline_depth = value;
427 } else {
428 return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
429 }
430 }
431
432 UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.AllHTTP11",
433 were_all_requests_http_1_1);
434 UMA_HISTOGRAM_ENUMERATION("NetConnectivity.Pipeline.Depth",
435 max_pipeline_depth, 6);
436
437 return internal::PipelineTestRequest::STATUS_SUCCESS;
438 }
439
440 } // namespace internal
441
442 namespace {
443
DeleteClient(IOThread * io_thread,int)444 void DeleteClient(IOThread* io_thread, int /* rv */) {
445 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
446 io_thread->globals()->http_pipelining_compatibility_client.reset();
447 }
448
CollectPipeliningCapabilityStatsOnIOThread(const std::string & pipeline_test_server,IOThread * io_thread)449 void CollectPipeliningCapabilityStatsOnIOThread(
450 const std::string& pipeline_test_server,
451 IOThread* io_thread) {
452 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
453
454 net::URLRequestContext* url_request_context =
455 io_thread->globals()->system_request_context.get();
456 if (!url_request_context->proxy_service()->config().proxy_rules().empty()) {
457 // Pipelining with explicitly configured proxies is disabled for now.
458 return;
459 }
460
461 const base::FieldTrial::Probability kDivisor = 100;
462 base::FieldTrial::Probability probability_to_run_test = 0;
463
464 const char* kTrialName = "HttpPipeliningCompatibility";
465 base::FieldTrial* trial = base::FieldTrialList::Find(kTrialName);
466 if (trial) {
467 return;
468 }
469 // After May 4, 2012, the trial will disable itself.
470 trial = base::FieldTrialList::FactoryGetFieldTrial(
471 kTrialName, kDivisor, "disable_test", 2012, 5, 4,
472 base::FieldTrial::SESSION_RANDOMIZED, NULL);
473
474 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
475 if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
476 probability_to_run_test = 100;
477 } else if (channel == chrome::VersionInfo::CHANNEL_DEV) {
478 probability_to_run_test = 100;
479 }
480
481 int collect_stats_group = trial->AppendGroup("enable_test",
482 probability_to_run_test);
483 if (trial->group() != collect_stats_group) {
484 return;
485 }
486
487 std::vector<RequestInfo> requests;
488
489 RequestInfo info0;
490 info0.filename = "alphabet.txt";
491 info0.expected_response = "abcdefghijklmnopqrstuvwxyz";
492 requests.push_back(info0);
493
494 RequestInfo info1;
495 info1.filename = "cached.txt";
496 info1.expected_response = "azbycxdwevfugthsirjqkplomn";
497 requests.push_back(info1);
498
499 RequestInfo info2;
500 info2.filename = "reverse.txt";
501 info2.expected_response = "zyxwvutsrqponmlkjihgfedcba";
502 requests.push_back(info2);
503
504 RequestInfo info3;
505 info3.filename = "chunked.txt";
506 info3.expected_response = "chunkedencodingisfun";
507 requests.push_back(info3);
508
509 RequestInfo info4;
510 info4.filename = "cached.txt";
511 info4.expected_response = "azbycxdwevfugthsirjqkplomn";
512 requests.push_back(info4);
513
514 HttpPipeliningCompatibilityClient* client =
515 new HttpPipeliningCompatibilityClient(NULL);
516 client->Start(pipeline_test_server, requests,
517 HttpPipeliningCompatibilityClient::PIPE_TEST_CANARY_AND_STATS,
518 base::Bind(&DeleteClient, io_thread),
519 url_request_context);
520 io_thread->globals()->http_pipelining_compatibility_client.reset(client);
521 }
522
523 } // anonymous namespace
524
CollectPipeliningCapabilityStatsOnUIThread(const std::string & pipeline_test_server,IOThread * io_thread)525 void CollectPipeliningCapabilityStatsOnUIThread(
526 const std::string& pipeline_test_server, IOThread* io_thread) {
527 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
528 if (pipeline_test_server.empty())
529 return;
530
531 content::BrowserThread::PostTask(
532 content::BrowserThread::IO,
533 FROM_HERE,
534 base::Bind(&CollectPipeliningCapabilityStatsOnIOThread,
535 pipeline_test_server,
536 io_thread));
537 }
538
539 } // namespace chrome_browser_net
540