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