Home | History | Annotate | Download | only in net
      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 
     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 
     53   const std::string& response() const { return response_; }
     54 
     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 
     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 
    107 void Request::Start() {
    108   url_request_->Start();
    109 }
    110 
    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 
    120 void Request::OnSSLCertificateError(
    121     net::URLRequest* request,
    122     const net::SSLInfo& ssl_info,
    123     bool fatal) {
    124   Finished(STATUS_CERT_ERROR);
    125 }
    126 
    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 
    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 
    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 
    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 
    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:
    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 
    213   virtual ~CanaryRequest() {}
    214 
    215  private:
    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.
    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 
    235   virtual ~StatsRequest() {}
    236 
    237  private:
    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:
    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 
    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 
    286 HttpPipeliningCompatibilityClient::~HttpPipeliningCompatibilityClient() {
    287 }
    288 
    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 
    347 void HttpPipeliningCompatibilityClient::StartTestRequests() {
    348   for (size_t i = 0; i < requests_.size(); ++i) {
    349     requests_[i]->Start();
    350   }
    351 }
    352 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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