root/chrome/browser/net/http_pipelining_compatibility_client.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. response
  2. delegate
  3. response_code_
  4. Start
  5. OnReceivedRedirect
  6. OnSSLCertificateError
  7. OnResponseStarted
  8. OnReadCompleted
  9. DoRead
  10. DoReadFinished
  11. Finished
  12. Finished
  13. DoReadFinished
  14. NewRequest
  15. num_succeeded_
  16. Start
  17. StartTestRequests
  18. OnCanaryFinished
  19. OnRequestFinished
  20. ReportNetworkError
  21. ReportResponseCode
  22. GetMetricName
  23. ProcessStatsResponse
  24. DeleteClient
  25. CollectPipeliningCapabilityStatsOnIOThread
  26. CollectPipeliningCapabilityStatsOnUIThread

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/net/http_pipelining_compatibility_client.h"

#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/io_thread.h"
#include "chrome/common/chrome_version_info.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/load_flags.h"
#include "net/base/network_change_notifier.h"
#include "net/base/request_priority.h"
#include "net/disk_cache/blockfile/histogram_macros.h"
#include "net/http/http_network_layer.h"
#include "net/http/http_network_session.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_version.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"

namespace chrome_browser_net {

static const int kCanaryRequestId = 999;

namespace {

// There is one Request per RequestInfo passed in to Start() above.
class Request : public internal::PipelineTestRequest,
                public net::URLRequest::Delegate {
 public:
  Request(int request_id,
          const std::string& base_url,
          const RequestInfo& info,
          internal::PipelineTestRequest::Delegate* delegate,
          net::URLRequestContext* url_request_context);

  virtual ~Request() {}

  virtual void Start() OVERRIDE;

 protected:
  // Called when this request has determined its result. Returns the result to
  // the |client_|.
  virtual void Finished(internal::PipelineTestRequest::Status result);

  const std::string& response() const { return response_; }

  internal::PipelineTestRequest::Delegate* delegate() { return delegate_; }

 private:
  // Called when a response can be read. Reads bytes into |response_| until it
  // consumes the entire response or it encounters an error.
  void DoRead();

  // Called when all bytes have been received. Compares the |response_| to
  // |info_|'s expected response.
  virtual void DoReadFinished();

  // net::URLRequest::Delegate interface
  virtual void OnReceivedRedirect(net::URLRequest* request,
                                  const GURL& new_url,
                                  bool* defer_redirect) OVERRIDE;
  virtual void OnSSLCertificateError(net::URLRequest* request,
                                     const net::SSLInfo& ssl_info,
                                     bool fatal) OVERRIDE;
  virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE;
  virtual void OnReadCompleted(net::URLRequest* request,
                               int bytes_read) OVERRIDE;

  internal::PipelineTestRequest::Delegate* delegate_;
  const int request_id_;
  scoped_ptr<net::URLRequest> url_request_;
  const RequestInfo info_;
  scoped_refptr<net::IOBuffer> read_buffer_;
  std::string response_;
  int response_code_;
};

Request::Request(int request_id,
                 const std::string& base_url,
                 const RequestInfo& info,
                 internal::PipelineTestRequest::Delegate* delegate,
                 net::URLRequestContext* url_request_context)
    : delegate_(delegate),
      request_id_(request_id),
      url_request_(url_request_context->CreateRequest(GURL(base_url +
                                                           info.filename),
                                                      net::DEFAULT_PRIORITY,
                                                      this,
                                                      NULL)),
      info_(info),
      response_code_(0) {
  url_request_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
                             net::LOAD_DISABLE_CACHE |
                             net::LOAD_DO_NOT_SAVE_COOKIES |
                             net::LOAD_DO_NOT_SEND_COOKIES |
                             net::LOAD_DO_NOT_PROMPT_FOR_LOGIN |
                             net::LOAD_DO_NOT_SEND_AUTH_DATA);
}

void Request::Start() {
  url_request_->Start();
}

void Request::OnReceivedRedirect(
    net::URLRequest* request,
    const GURL& new_url,
    bool* defer_redirect) {
  *defer_redirect = true;
  request->Cancel();
  Finished(STATUS_REDIRECTED);
}

void Request::OnSSLCertificateError(
    net::URLRequest* request,
    const net::SSLInfo& ssl_info,
    bool fatal) {
  Finished(STATUS_CERT_ERROR);
}

void Request::OnResponseStarted(net::URLRequest* request) {
  response_code_ = request->GetResponseCode();
  if (response_code_ != 200) {
    Finished(STATUS_BAD_RESPONSE_CODE);
    return;
  }
  const net::HttpVersion required_version(1, 1);
  if (request->response_info().headers->GetParsedHttpVersion() <
      required_version) {
    Finished(STATUS_BAD_HTTP_VERSION);
    return;
  }
  read_buffer_ = new net::IOBuffer(info_.expected_response.length());
  DoRead();
}

void Request::OnReadCompleted(net::URLRequest* request, int bytes_read) {
  if (bytes_read == 0) {
    DoReadFinished();
  } else if (bytes_read < 0) {
    Finished(STATUS_NETWORK_ERROR);
  } else {
    response_.append(read_buffer_->data(), bytes_read);
    if (response_.length() <= info_.expected_response.length()) {
      DoRead();
    } else if (response_.find(info_.expected_response) == 0) {
      Finished(STATUS_TOO_LARGE);
    } else {
      Finished(STATUS_CONTENT_MISMATCH);
    }
  }
}

void Request::DoRead() {
  int bytes_read = 0;
  if (url_request_->Read(read_buffer_.get(), info_.expected_response.length(),
                         &bytes_read)) {
    OnReadCompleted(url_request_.get(), bytes_read);
  }
}

void Request::DoReadFinished() {
  if (response_.length() != info_.expected_response.length()) {
    if (info_.expected_response.find(response_) == 0) {
      Finished(STATUS_TOO_SMALL);
    } else {
      Finished(STATUS_CONTENT_MISMATCH);
    }
  } else if (response_ == info_.expected_response) {
    Finished(STATUS_SUCCESS);
  } else {
    Finished(STATUS_CONTENT_MISMATCH);
  }
}

void Request::Finished(internal::PipelineTestRequest::Status result) {
  const net::URLRequestStatus status = url_request_->status();
  url_request_.reset();
  if (response_code_ > 0) {
    delegate()->ReportResponseCode(request_id_, response_code_);
  }
  if (status.status() == net::URLRequestStatus::FAILED) {
    // Network errors trump all other status codes, because network errors can
    // be detected by the network stack even with real content. If we determine
    // that all pipelining errors can be detected by the network stack, then we
    // don't need to worry about broken proxies.
    delegate()->ReportNetworkError(request_id_, status.error());
    delegate()->OnRequestFinished(request_id_, STATUS_NETWORK_ERROR);
  } else {
    delegate()->OnRequestFinished(request_id_, result);
  }
  // WARNING: We may be deleted at this point.
}

// A special non-pipelined request sent before pipelining begins to test basic
// HTTP connectivity.
class CanaryRequest : public Request {
 public:
  CanaryRequest(int request_id,
               const std::string& base_url,
               const RequestInfo& info,
               internal::PipelineTestRequest::Delegate* delegate,
               net::URLRequestContext* url_request_context)
      : Request(request_id, base_url, info, delegate, url_request_context) {
  }

  virtual ~CanaryRequest() {}

 private:
  virtual void Finished(
      internal::PipelineTestRequest::Status result) OVERRIDE {
    delegate()->OnCanaryFinished(result);
  }
};

// A special request that parses a /stats.txt response from the test server.
class StatsRequest : public Request {
 public:
  // Note that |info.expected_response| is only used to determine the correct
  // 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)
      : Request(request_id, base_url, info, delegate, url_request_context) {
  }

  virtual ~StatsRequest() {}

 private:
  virtual void DoReadFinished() OVERRIDE {
    internal::PipelineTestRequest::Status status =
        internal::ProcessStatsResponse(response());
    Finished(status);
  }
};

class RequestFactory : public internal::PipelineTestRequest::Factory {
 public:
  virtual internal::PipelineTestRequest* 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) OVERRIDE {
    switch (request_type) {
      case internal::PipelineTestRequest::TYPE_PIPELINED:
        return new Request(request_id, base_url, info, delegate,
                           url_request_context);

      case internal::PipelineTestRequest::TYPE_CANARY:
        return new CanaryRequest(request_id, base_url, info, delegate,
                                 url_request_context);

      case internal::PipelineTestRequest::TYPE_STATS:
        return new StatsRequest(request_id, base_url, info, delegate,
                                url_request_context);

      default:
        NOTREACHED();
        return NULL;
    }
  }
};

}  // anonymous namespace

HttpPipeliningCompatibilityClient::HttpPipeliningCompatibilityClient(
    internal::PipelineTestRequest::Factory* factory)
    : factory_(factory),
      num_finished_(0),
      num_succeeded_(0) {
  if (!factory_.get()) {
    factory_.reset(new RequestFactory);
  }
}

HttpPipeliningCompatibilityClient::~HttpPipeliningCompatibilityClient() {
}

void HttpPipeliningCompatibilityClient::Start(
    const std::string& base_url,
    std::vector<RequestInfo>& requests,
    Options options,
    const net::CompletionCallback& callback,
    net::URLRequestContext* url_request_context) {
  net::HttpNetworkSession* old_session =
      url_request_context->http_transaction_factory()->GetSession();
  net::HttpNetworkSession::Params params = old_session->params();
  params.force_http_pipelining = true;
  scoped_refptr<net::HttpNetworkSession> session =
      new net::HttpNetworkSession(params);
  http_transaction_factory_.reset(
      net::HttpNetworkLayer::CreateFactory(session.get()));

  url_request_context_.reset(new net::URLRequestContext);
  url_request_context_->CopyFrom(url_request_context);
  url_request_context_->set_http_transaction_factory(
      http_transaction_factory_.get());

  finished_callback_ = callback;
  for (size_t i = 0; i < requests.size(); ++i) {
    requests_.push_back(factory_->NewRequest(
        i, base_url, requests[i], this, url_request_context_.get(),
        internal::PipelineTestRequest::TYPE_PIPELINED));
  }
  if (options == PIPE_TEST_COLLECT_SERVER_STATS ||
      options == PIPE_TEST_CANARY_AND_STATS) {
    RequestInfo info;
    info.filename = "stats.txt";
    // This is just to determine the expected length of the response.
    // StatsRequest doesn't expect this exact value, but it does expect this
    // exact length.
    info.expected_response =
        "were_all_requests_http_1_1:1,max_pipeline_depth:5";
    requests_.push_back(factory_->NewRequest(
        requests.size(), base_url, info, this, url_request_context_.get(),
        internal::PipelineTestRequest::TYPE_STATS));
  }
  if (options == PIPE_TEST_RUN_CANARY_REQUEST ||
      options == PIPE_TEST_CANARY_AND_STATS) {
    RequestInfo info;
    info.filename = "index.html";
    info.expected_response =
        "\nThis is a test server operated by Google. It's used by Google "
        "Chrome to test\nproxies for compatibility with HTTP pipelining. More "
        "information can be found\nhere:\n\nhttp://dev.chromium.org/developers/"
        "design-documents/network-stack/http-pipelining\n\nSource code can be "
        "found here:\n\nhttp://code.google.com/p/http-pipelining-test/\n";
    canary_request_.reset(factory_->NewRequest(
        kCanaryRequestId, base_url, info, this, url_request_context,
        internal::PipelineTestRequest::TYPE_CANARY));
    canary_request_->Start();
  } else {
    StartTestRequests();
  }
}

void HttpPipeliningCompatibilityClient::StartTestRequests() {
  for (size_t i = 0; i < requests_.size(); ++i) {
    requests_[i]->Start();
  }
}

void HttpPipeliningCompatibilityClient::OnCanaryFinished(
    internal::PipelineTestRequest::Status status) {
  canary_request_.reset();
  bool success = (status == internal::PipelineTestRequest::STATUS_SUCCESS);
  UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.CanarySuccess", success);
  if (success) {
    StartTestRequests();
  } else {
    finished_callback_.Run(0);
  }
}

void HttpPipeliningCompatibilityClient::OnRequestFinished(
    int request_id, internal::PipelineTestRequest::Status status) {
  // The CACHE_HISTOGRAM_* macros are used, because they allow dynamic metric
  // names.
  // TODO(gavinp): Clean up this dependency by moving the needed functionality
  // into base/.
  CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "Status"),
                              status,
                              internal::PipelineTestRequest::STATUS_MAX);

  ++num_finished_;
  if (status == internal::PipelineTestRequest::STATUS_SUCCESS) {
    ++num_succeeded_;
  }
  if (num_finished_ == requests_.size()) {
    UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.Success",
                          num_succeeded_ == requests_.size());
    finished_callback_.Run(0);
  }
}

void HttpPipeliningCompatibilityClient::ReportNetworkError(int request_id,
                                                           int error_code) {
  CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "NetworkError"),
                              -error_code, 900);
}

void HttpPipeliningCompatibilityClient::ReportResponseCode(int request_id,
                                                           int response_code) {
  CACHE_HISTOGRAM_ENUMERATION(GetMetricName(request_id, "ResponseCode"),
                              response_code, 600);
}

std::string HttpPipeliningCompatibilityClient::GetMetricName(
    int request_id, const char* description) {
  return base::StringPrintf("NetConnectivity.Pipeline.%d.%s",
                            request_id, description);
}

namespace internal {

internal::PipelineTestRequest::Status ProcessStatsResponse(
    const std::string& response) {
  bool were_all_requests_http_1_1 = false;
  int max_pipeline_depth = 0;

  std::vector<std::pair<std::string, std::string> > kv_pairs;
  base::SplitStringIntoKeyValuePairs(response, ':', ',', &kv_pairs);

  if (kv_pairs.size() != 2) {
    return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
  }

  for (size_t i = 0; i < kv_pairs.size(); ++i) {
    const std::string& key = kv_pairs[i].first;
    int value;
    if (!base::StringToInt(kv_pairs[i].second, &value)) {
      return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
    }

    if (key == "were_all_requests_http_1_1") {
      were_all_requests_http_1_1 = (value == 1);
    } else if (key == "max_pipeline_depth") {
      max_pipeline_depth = value;
    } else {
      return internal::PipelineTestRequest::STATUS_CORRUPT_STATS;
    }
  }

  UMA_HISTOGRAM_BOOLEAN("NetConnectivity.Pipeline.AllHTTP11",
                        were_all_requests_http_1_1);
  UMA_HISTOGRAM_ENUMERATION("NetConnectivity.Pipeline.Depth",
                            max_pipeline_depth, 6);

  return internal::PipelineTestRequest::STATUS_SUCCESS;
}

}  // namespace internal

namespace {

void DeleteClient(IOThread* io_thread, int /* rv */) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  io_thread->globals()->http_pipelining_compatibility_client.reset();
}

void CollectPipeliningCapabilityStatsOnIOThread(
    const std::string& pipeline_test_server,
    IOThread* io_thread) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));

  net::URLRequestContext* url_request_context =
      io_thread->globals()->system_request_context.get();
  if (!url_request_context->proxy_service()->config().proxy_rules().empty()) {
    // Pipelining with explicitly configured proxies is disabled for now.
    return;
  }

  const base::FieldTrial::Probability kDivisor = 100;
  base::FieldTrial::Probability probability_to_run_test = 0;

  const char* kTrialName = "HttpPipeliningCompatibility";
  base::FieldTrial* trial = base::FieldTrialList::Find(kTrialName);
  if (trial) {
    return;
  }
  // After May 4, 2012, the trial will disable itself.
  trial = base::FieldTrialList::FactoryGetFieldTrial(
      kTrialName, kDivisor, "disable_test", 2012, 5, 4,
      base::FieldTrial::SESSION_RANDOMIZED, NULL);

  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
  if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
    probability_to_run_test = 100;
  } else if (channel == chrome::VersionInfo::CHANNEL_DEV) {
    probability_to_run_test = 100;
  }

  int collect_stats_group = trial->AppendGroup("enable_test",
                                               probability_to_run_test);
  if (trial->group() != collect_stats_group) {
    return;
  }

  std::vector<RequestInfo> requests;

  RequestInfo info0;
  info0.filename = "alphabet.txt";
  info0.expected_response = "abcdefghijklmnopqrstuvwxyz";
  requests.push_back(info0);

  RequestInfo info1;
  info1.filename = "cached.txt";
  info1.expected_response = "azbycxdwevfugthsirjqkplomn";
  requests.push_back(info1);

  RequestInfo info2;
  info2.filename = "reverse.txt";
  info2.expected_response = "zyxwvutsrqponmlkjihgfedcba";
  requests.push_back(info2);

  RequestInfo info3;
  info3.filename = "chunked.txt";
  info3.expected_response = "chunkedencodingisfun";
  requests.push_back(info3);

  RequestInfo info4;
  info4.filename = "cached.txt";
  info4.expected_response = "azbycxdwevfugthsirjqkplomn";
  requests.push_back(info4);

  HttpPipeliningCompatibilityClient* client =
      new HttpPipeliningCompatibilityClient(NULL);
  client->Start(pipeline_test_server, requests,
                HttpPipeliningCompatibilityClient::PIPE_TEST_CANARY_AND_STATS,
                base::Bind(&DeleteClient, io_thread),
                url_request_context);
  io_thread->globals()->http_pipelining_compatibility_client.reset(client);
}

}  // anonymous namespace

void CollectPipeliningCapabilityStatsOnUIThread(
    const std::string& pipeline_test_server, IOThread* io_thread) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  if (pipeline_test_server.empty())
    return;

  content::BrowserThread::PostTask(
      content::BrowserThread::IO,
      FROM_HERE,
      base::Bind(&CollectPipeliningCapabilityStatsOnIOThread,
                 pipeline_test_server,
                 io_thread));
}

}  // namespace chrome_browser_net

/* [<][>][^][v][top][bottom][index][help] */