summaryrefslogtreecommitdiffstats
path: root/chrome/browser/net/connection_tester.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/net/connection_tester.cc')
-rw-r--r--chrome/browser/net/connection_tester.cc398
1 files changed, 398 insertions, 0 deletions
diff --git a/chrome/browser/net/connection_tester.cc b/chrome/browser/net/connection_tester.cc
new file mode 100644
index 0000000..162be2c
--- /dev/null
+++ b/chrome/browser/net/connection_tester.cc
@@ -0,0 +1,398 @@
+// Copyright (c) 2010 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/connection_tester.h"
+
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "chrome/browser/importer/firefox_proxy_settings.h"
+#include "chrome/common/chrome_switches.h"
+#include "net/base/cookie_monster.h"
+#include "net/base/host_resolver_impl.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+namespace {
+
+// ExperimentURLRequestContext ------------------------------------------------
+
+// An instance of ExperimentURLRequestContext is created for each experiment
+// run by ConnectionTester. The class initializes network dependencies according
+// to the specified "experiment".
+class ExperimentURLRequestContext : public URLRequestContext {
+ public:
+ int Init(const ConnectionTester::Experiment& experiment) {
+ int rv;
+
+ // Create a custom HostResolver for this experiment.
+ rv = CreateHostResolver(experiment.host_resolver_experiment,
+ &host_resolver_);
+ if (rv != net::OK)
+ return rv; // Failure.
+
+ // Create a custom ProxyService for this this experiment.
+ rv = CreateProxyService(experiment.proxy_settings_experiment,
+ &proxy_service_);
+ if (rv != net::OK)
+ return rv; // Failure.
+
+ // The rest of the dependencies are standard, and don't depend on the
+ // experiment being run.
+ ftp_transaction_factory_ = new net::FtpNetworkLayer(host_resolver_);
+ ssl_config_service_ = new net::SSLConfigServiceDefaults;
+ http_auth_handler_factory_ = net::HttpAuthHandlerFactory::CreateDefault();
+ http_transaction_factory_ = new net::HttpCache(
+ net::HttpNetworkLayer::CreateFactory(host_resolver_, proxy_service_,
+ ssl_config_service_, http_auth_handler_factory_, NULL, NULL),
+ net::HttpCache::DefaultBackend::InMemory(0));
+ // In-memory cookie store.
+ cookie_store_ = new net::CookieMonster(NULL, NULL);
+
+ return net::OK;
+ }
+
+ protected:
+ virtual ~ExperimentURLRequestContext() {
+ delete ftp_transaction_factory_;
+ delete http_transaction_factory_;
+ delete http_auth_handler_factory_;
+ }
+
+ private:
+ // Creates a host resolver for |experiment|. On success returns net::OK and
+ // fills |host_resolver| with a new pointer. Otherwise returns a network
+ // error code.
+ int CreateHostResolver(
+ ConnectionTester::HostResolverExperiment experiment,
+ scoped_refptr<net::HostResolver>* host_resolver) {
+ // Create a vanilla HostResolver that disables caching.
+ const size_t kMaxJobs = 50u;
+ scoped_refptr<net::HostResolverImpl> impl =
+ new net::HostResolverImpl(NULL, NULL, kMaxJobs);
+
+ *host_resolver = impl;
+
+ // Modify it slightly based on the experiment being run.
+ switch (experiment) {
+ case ConnectionTester::HOST_RESOLVER_EXPERIMENT_PLAIN:
+ return net::OK;
+ case ConnectionTester::HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6:
+ impl->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4);
+ return net::OK;
+ case ConnectionTester::HOST_RESOLVER_EXPERIMENT_IPV6_PROBE: {
+ // Note that we don't use impl->ProbeIPv6Support() since that finishes
+ // asynchronously and may not take effect in time for the test.
+ // So instead we will probe synchronously (might take 100-200 ms).
+ net::AddressFamily family = net::IPv6Supported() ?
+ net::ADDRESS_FAMILY_UNSPECIFIED : net::ADDRESS_FAMILY_IPV4;
+ impl->SetDefaultAddressFamily(family);
+ return net::OK;
+ }
+ default:
+ NOTREACHED();
+ return net::ERR_UNEXPECTED;
+ }
+ }
+
+ // Creates a proxy config service for |experiment|. On success returns net::OK
+ // and fills |config_service| with a new pointer. Otherwise returns a network
+ // error code.
+ int CreateProxyConfigService(
+ ConnectionTester::ProxySettingsExperiment experiment,
+ scoped_ptr<net::ProxyConfigService>* config_service) {
+ switch (experiment) {
+ case ConnectionTester::PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
+ return CreateSystemProxyConfigService(config_service);
+ case ConnectionTester::PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
+ return CreateFirefoxProxyConfigService(config_service);
+ case ConnectionTester::PROXY_EXPERIMENT_USE_AUTO_DETECT:
+ config_service->reset(new net::ProxyConfigServiceFixed(
+ net::ProxyConfig::CreateAutoDetect()));
+ return net::OK;
+ case ConnectionTester::PROXY_EXPERIMENT_USE_DIRECT:
+ config_service->reset(new net::ProxyConfigServiceFixed(
+ net::ProxyConfig::CreateDirect()));
+ return net::OK;
+ default:
+ NOTREACHED();
+ return net::ERR_UNEXPECTED;
+ }
+ }
+
+ // Creates a proxy service for |experiment|. On success returns net::OK
+ // and fills |config_service| with a new pointer. Otherwise returns a network
+ // error code.
+ int CreateProxyService(
+ ConnectionTester::ProxySettingsExperiment experiment,
+ scoped_refptr<net::ProxyService>* proxy_service) {
+ // Create an appropriate proxy config service.
+ scoped_ptr<net::ProxyConfigService> config_service;
+ int rv = CreateProxyConfigService(experiment, &config_service);
+ if (rv != net::OK)
+ return rv; // Failure.
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSingleProcess)) {
+ // We can't create a standard proxy resolver in single-process mode.
+ // Rather than falling-back to some other implementation, fail.
+ return net::ERR_NOT_IMPLEMENTED;
+ }
+
+ *proxy_service = net::ProxyService::Create(config_service.release(), true,
+ 0u, this, NULL, MessageLoop::current());
+
+ return net::OK;
+ }
+
+ // Creates a proxy config service that pulls from the system proxy settings.
+ // On success returns net::OK and fills |config_service| with a new pointer.
+ // Otherwise returns a network error code.
+ int CreateSystemProxyConfigService(
+ scoped_ptr<net::ProxyConfigService>* config_service) {
+#if defined(OS_LINUX)
+ // TODO(eroman): This is not supported on Linux yet, because of how
+ // construction needs ot happen on the UI thread.
+ return net::ERR_NOT_IMPLEMENTED;
+#else
+ config_service->reset(
+ net::ProxyService::CreateSystemProxyConfigService(NULL, NULL));
+ return net::OK;
+#endif
+ }
+
+ // Creates a fixed proxy config service that is initialized using Firefox's
+ // current proxy settings. On success returns net::OK and fills
+ // |config_service| with a new pointer. Otherwise returns a network error
+ // code.
+ int CreateFirefoxProxyConfigService(
+ scoped_ptr<net::ProxyConfigService>* config_service) {
+ // Fetch Firefox's proxy settings (can fail if Firefox is not installed).
+ FirefoxProxySettings firefox_settings;
+ if (!FirefoxProxySettings::GetSettings(&firefox_settings))
+ return net::ERR_FILE_NOT_FOUND;
+
+ if (FirefoxProxySettings::SYSTEM == firefox_settings.config_type())
+ return CreateSystemProxyConfigService(config_service);
+
+ net::ProxyConfig config;
+ if (firefox_settings.ToProxyConfig(&config)) {
+ config_service->reset(new net::ProxyConfigServiceFixed(config));
+ return net::OK;
+ }
+
+ return net::ERR_FAILED;
+ }
+};
+
+} // namespace
+
+// ConnectionTester::TestRunner ----------------------------------------------
+
+// TestRunner is a helper class for running an individual experiment. It can
+// be deleted any time after it is started, and this will abort the request.
+class ConnectionTester::TestRunner : public URLRequest::Delegate {
+ public:
+ // |tester| must remain alive throughout the TestRunner's lifetime.
+ // |tester| will be notified of completion.
+ explicit TestRunner(ConnectionTester* tester) : tester_(tester) {}
+
+ // Starts running |experiment|. Notifies tester->OnExperimentCompleted() when
+ // it is done.
+ void Run(const Experiment& experiment);
+
+ // URLRequest::Delegate implementation.
+ virtual void OnResponseStarted(URLRequest* request);
+ virtual void OnReadCompleted(URLRequest* request, int bytes_read);
+ // TODO(eroman): handle cases requiring authentication.
+
+ private:
+ // The number of bytes to read each response body chunk.
+ static const int kReadBufferSize = 1024;
+
+ // Starts reading the response's body (and keeps reading until an error or
+ // end of stream).
+ void ReadBody(URLRequest* request);
+
+ // Called when the request has completed (for both success and failure).
+ void OnResponseCompleted(URLRequest* request);
+
+ ConnectionTester* tester_;
+ scoped_ptr<URLRequest> request_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestRunner);
+};
+
+void ConnectionTester::TestRunner::OnResponseStarted(URLRequest* request) {
+ if (!request->status().is_success()) {
+ OnResponseCompleted(request);
+ return;
+ }
+
+ // Start reading the body.
+ ReadBody(request);
+}
+
+void ConnectionTester::TestRunner::OnReadCompleted(URLRequest* request,
+ int bytes_read) {
+ if (bytes_read <= 0) {
+ OnResponseCompleted(request);
+ return;
+ }
+
+ // Keep reading until the stream is closed. Throw the data read away.
+ ReadBody(request);
+}
+
+void ConnectionTester::TestRunner::ReadBody(URLRequest* request) {
+ // Read the response body |kReadBufferSize| bytes at a time.
+ scoped_refptr<net::IOBuffer> unused_buffer =
+ new net::IOBuffer(kReadBufferSize);
+ int num_bytes;
+ if (request->Read(unused_buffer, kReadBufferSize, &num_bytes)) {
+ OnReadCompleted(request, num_bytes);
+ } else if (!request->status().is_io_pending()) {
+ // Read failed synchronously.
+ OnResponseCompleted(request);
+ }
+}
+
+void ConnectionTester::TestRunner::OnResponseCompleted(URLRequest* request) {
+ int result = net::OK;
+ if (!request->status().is_success()) {
+ DCHECK_NE(net::ERR_IO_PENDING, request->status().os_error());
+ result = request->status().os_error();
+ }
+ tester_->OnExperimentCompleted(result);
+}
+
+void ConnectionTester::TestRunner::Run(const Experiment& experiment) {
+ // Try to create a URLRequestContext for this experiment.
+ scoped_refptr<ExperimentURLRequestContext> context =
+ new ExperimentURLRequestContext();
+ int rv = context->Init(experiment);
+ if (rv != net::OK) {
+ // Complete the experiment with a failure.
+ tester_->OnExperimentCompleted(rv);
+ return;
+ }
+
+ // Fetch a request using the experimental context.
+ request_.reset(new URLRequest(experiment.url, this));
+ request_->set_context(context);
+ request_->Start();
+}
+
+// ConnectionTester ----------------------------------------------------------
+
+ConnectionTester::ConnectionTester(Delegate* delegate)
+ : delegate_(delegate) {
+ DCHECK(delegate);
+}
+
+ConnectionTester::~ConnectionTester() {
+ // Cancellation happens automatically by deleting test_runner_.
+}
+
+void ConnectionTester::RunAllTests(const GURL& url) {
+ // Select all possible experiments to run. (In no particular order).
+ // It is possible that some of these experiments are actually duplicates.
+ GetAllPossibleExperimentCombinations(url, &remaining_experiments_);
+
+ delegate_->OnStartConnectionTestSuite();
+ StartNextExperiment();
+}
+
+// static
+string16 ConnectionTester::ProxySettingsExperimentDescription(
+ ProxySettingsExperiment experiment) {
+ // TODO(eroman): Use proper string resources.
+ switch (experiment) {
+ case PROXY_EXPERIMENT_USE_DIRECT:
+ return ASCIIToUTF16("Don't use any proxy");
+ case PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
+ return ASCIIToUTF16("Use system proxy settings");
+ case PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
+ return ASCIIToUTF16("Use Firefox's proxy settings");
+ case PROXY_EXPERIMENT_USE_AUTO_DETECT:
+ return ASCIIToUTF16("Auto-detect proxy settings");
+ default:
+ NOTREACHED();
+ return string16();
+ }
+}
+
+// static
+string16 ConnectionTester::HostResolverExperimentDescription(
+ HostResolverExperiment experiment) {
+ // TODO(eroman): Use proper string resources.
+ switch (experiment) {
+ case HOST_RESOLVER_EXPERIMENT_PLAIN:
+ return string16();
+ case HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6:
+ return ASCIIToUTF16("Disable IPv6 host resolving");
+ case HOST_RESOLVER_EXPERIMENT_IPV6_PROBE:
+ return ASCIIToUTF16("Probe for IPv6 host resolving");
+ default:
+ NOTREACHED();
+ return string16();
+ }
+}
+
+// static
+void ConnectionTester::GetAllPossibleExperimentCombinations(
+ const GURL& url,
+ ConnectionTester::ExperimentList* list) {
+ list->clear();
+ for (size_t resolver_experiment = 0;
+ resolver_experiment < HOST_RESOLVER_EXPERIMENT_COUNT;
+ ++resolver_experiment) {
+ for (size_t proxy_experiment = 0;
+ proxy_experiment < PROXY_EXPERIMENT_COUNT;
+ ++proxy_experiment) {
+ Experiment experiment(
+ url,
+ static_cast<ProxySettingsExperiment>(proxy_experiment),
+ static_cast<HostResolverExperiment>(resolver_experiment));
+ list->push_back(experiment);
+ }
+ }
+}
+
+void ConnectionTester::StartNextExperiment() {
+ DCHECK(!remaining_experiments_.empty());
+ DCHECK(!current_test_runner_.get());
+
+ delegate_->OnStartConnectionTestExperiment(current_experiment());
+
+ current_test_runner_.reset(new TestRunner(this));
+ current_test_runner_->Run(current_experiment());
+}
+
+void ConnectionTester::OnExperimentCompleted(int result) {
+ Experiment current = current_experiment();
+
+ // Advance to the next experiment.
+ remaining_experiments_.erase(remaining_experiments_.begin());
+ current_test_runner_.reset();
+
+ // Notify the delegate of completion.
+ delegate_->OnCompletedConnectionTestExperiment(current, result);
+
+ if (remaining_experiments_.empty()) {
+ delegate_->OnCompletedConnectionTestSuite();
+ } else {
+ StartNextExperiment();
+ }
+}