• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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