diff options
author | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-31 01:34:20 +0000 |
---|---|---|
committer | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-31 01:34:20 +0000 |
commit | 41b2780f0135d23196958816d3adcfd606b80f1e (patch) | |
tree | 306a00be7404dfc6d3ff9ef667d35827f87281a2 /chrome/browser/net | |
parent | 18a12354e6c3e1edf2f85e11cd7359127d2d3ce2 (diff) | |
download | chromium_src-41b2780f0135d23196958816d3adcfd606b80f1e.zip chromium_src-41b2780f0135d23196958816d3adcfd606b80f1e.tar.gz chromium_src-41b2780f0135d23196958816d3adcfd606b80f1e.tar.bz2 |
Move proxy resolve requests out of plugin/renderer process, and into the browser.
Review URL: http://codereview.chromium.org/14142
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@9006 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/net')
-rw-r--r-- | chrome/browser/net/resolve_proxy_msg_helper.cc | 89 | ||||
-rw-r--r-- | chrome/browser/net/resolve_proxy_msg_helper.h | 104 | ||||
-rw-r--r-- | chrome/browser/net/resolve_proxy_msg_helper_unittest.cc | 349 |
3 files changed, 542 insertions, 0 deletions
diff --git a/chrome/browser/net/resolve_proxy_msg_helper.cc b/chrome/browser/net/resolve_proxy_msg_helper.cc new file mode 100644 index 0000000..dca5ad6 --- /dev/null +++ b/chrome/browser/net/resolve_proxy_msg_helper.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2009 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/resolve_proxy_msg_helper.h" + +#include "base/compiler_specific.h" +#include "chrome/browser/profile.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request_context.h" + +ResolveProxyMsgHelper::ResolveProxyMsgHelper(Delegate* delegate, + net::ProxyService* proxy_service) + : proxy_service_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_( + this, &ResolveProxyMsgHelper::OnResolveProxyCompleted)), + delegate_(delegate), + proxy_service_override_(proxy_service) { +} + +void ResolveProxyMsgHelper::Start(const GURL& url, IPC::Message* reply_msg) { + // Enqueue the pending request. + pending_requests_.push_back(PendingRequest(url, reply_msg)); + + // If nothing is in progress, start. + if (pending_requests_.size() == 1) + StartPendingRequest(); +} + +void ResolveProxyMsgHelper::OnResolveProxyCompleted(int result) { + CHECK(!pending_requests_.empty()); + + // Notify the delegate of completion. + const PendingRequest& completed_req = pending_requests_.front(); + delegate_->OnResolveProxyCompleted(completed_req.reply_msg, + result, + proxy_info_.GetAnnotatedProxyList()); + + // Clear the current (completed) request. + pending_requests_.pop_front(); + proxy_service_ = NULL; + + // Start the next request. + if (!pending_requests_.empty()) + StartPendingRequest(); +} + +void ResolveProxyMsgHelper::StartPendingRequest() { + PendingRequest& req = pending_requests_.front(); + + // Verify the request wasn't started yet. + DCHECK(NULL == req.pac_req); + DCHECK(NULL == proxy_service_); + + // Start the request. + proxy_service_ = GetProxyService(); + int result = proxy_service_->ResolveProxy( + req.url, &proxy_info_, &callback_, &req.pac_req); + + // Completed synchronously. + if (result != net::ERR_IO_PENDING) + OnResolveProxyCompleted(result); +} + +net::ProxyService* ResolveProxyMsgHelper::GetProxyService() const { + // Unit-tests specify their own proxy service to use. + if (proxy_service_override_) + return proxy_service_override_; + + // Otherwise use the browser's global proxy service. + return Profile::GetDefaultRequestContext()->proxy_service(); +} + +ResolveProxyMsgHelper::~ResolveProxyMsgHelper() { + // Clear all pending requests. + if (!pending_requests_.empty()) { + PendingRequest req = pending_requests_.front(); + proxy_service_->CancelPacRequest(req.pac_req); + } + + for (PendingRequestList::iterator it = pending_requests_.begin(); + it != pending_requests_.end(); + ++it) { + delete it->reply_msg; + } + + proxy_service_ = NULL; + pending_requests_.clear(); +} diff --git a/chrome/browser/net/resolve_proxy_msg_helper.h b/chrome/browser/net/resolve_proxy_msg_helper.h new file mode 100644 index 0000000..bf4c2f8 --- /dev/null +++ b/chrome/browser/net/resolve_proxy_msg_helper.h @@ -0,0 +1,104 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_NET_RESOLVE_PROXY_MSG_HELPER_ +#define CHROME_BROWSER_NET_RESOLVE_PROXY_MSG_HELPER_ + +#include <deque> +#include <string> + +#include "chrome/common/ipc_message.h" +#include "net/base/completion_callback.h" +#include "googleurl/src/gurl.h" +#include "net/proxy/proxy_service.h" + +// This class holds the common logic used to respond to the messages: +// {PluginProcessHostMsg_ResolveProxy, ViewHostMsg_ResolveProxy}. +// +// This involves kicking off a ProxyResolve request on the IO thread using +// the specified proxy service. +// +// When the request completes, it calls the delegate's OnProxyResolveCompleted() +// method, passing it the result (error code + proxy list), as well as the +// IPC::Message pointer that had been stored. +// +// When an instance of ResolveProxyMsgHelper is destroyed, it cancels any +// outstanding proxy resolve requests with the proxy service. It also deletes +// the stored IPC::Message pointers for pending requests. +// +// This object is expected to live on the IO thread. +class ResolveProxyMsgHelper { + public: + class Delegate { + public: + // Callback for when the proxy resolve request has completed. + // |reply_msg| -- The same pointer that the request was started with. + // |result| -- The network error code from ProxyService. + // |proxy_list| -- The PAC string result from ProxyService. + virtual void OnResolveProxyCompleted(IPC::Message* reply_msg, + int result, + const std::string& proxy_list) = 0; + virtual ~Delegate() {} + }; + + // Construct a ResolveProxyMsgHelper instance that notifies request + // completion to |delegate|. Note that |delegate| must be live throughout + // our lifespan. If |proxy_service| is NULL, then the current profile's + // proxy service will be used. + explicit ResolveProxyMsgHelper(Delegate* delegate, + net::ProxyService* proxy_service); + + // Resolve proxies for |url|. Completion is notified through the delegate. + // If multiple requests are started at the same time, they will run in + // FIFO order, with only 1 being outstanding at a time. + void Start(const GURL& url, IPC::Message* reply_msg); + + // Destruction cancels the current outstanding request, and clears the + // pending queue. + ~ResolveProxyMsgHelper(); + + private: + // Callback for the ProxyService (bound to |callback_|). + void OnResolveProxyCompleted(int result); + + // Start the first pending request. + void StartPendingRequest(); + + // Get the proxy service instance to use. + net::ProxyService* GetProxyService() const; + + // A PendingRequest is a resolve request that is in progress, or queued. + struct PendingRequest { + public: + PendingRequest(const GURL& url, IPC::Message* reply_msg) : + url(url), reply_msg(reply_msg), pac_req(NULL) { } + + // The URL of the request. + GURL url; + + // Data to pass back to the delegate on completion (we own it until then). + IPC::Message* reply_msg; + + // Handle for cancelling the current request if it has started (else NULL). + net::ProxyService::PacRequest* pac_req; + }; + + // Members for the current outstanding proxy request. + net::ProxyService* proxy_service_; + net::CompletionCallbackImpl<ResolveProxyMsgHelper> callback_; + net::ProxyInfo proxy_info_; + + // FIFO queue of pending requests. The first entry is always the current one. + typedef std::deque<PendingRequest> PendingRequestList; + PendingRequestList pending_requests_; + + Delegate* delegate_; + + // Specified by unit-tests, to use this proxy service in place of the + // global one. + net::ProxyService* proxy_service_override_; +}; + +#endif // CHROME_BROWSER_NET_RESOLVE_PROXY_MSG_HELPER_ + diff --git a/chrome/browser/net/resolve_proxy_msg_helper_unittest.cc b/chrome/browser/net/resolve_proxy_msg_helper_unittest.cc new file mode 100644 index 0000000..dd70713 --- /dev/null +++ b/chrome/browser/net/resolve_proxy_msg_helper_unittest.cc @@ -0,0 +1,349 @@ +// Copyright (c) 2009 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/resolve_proxy_msg_helper.h" + +#include "base/waitable_event.h" +#include "net/base/net_errors.h" +#include "testing/gtest/include/gtest/gtest.h" + +// This ProxyConfigService always returns "http://pac" as the PAC url to use. +class MockProxyConfigService: public net::ProxyConfigService { + public: + virtual int GetProxyConfig(net::ProxyConfig* results) { + results->pac_url = GURL("http://pac"); + return net::OK; + } +}; + +// This PAC resolver always returns the hostname of the query URL as the +// proxy to use. The Block() method will make GetProxyForURL() hang until +// Unblock() is called. +class MockProxyResolver : public net::ProxyResolver { + public: + explicit MockProxyResolver() : event_(false, false), is_blocked_(false) { + } + + virtual int GetProxyForURL(const GURL& query_url, + const GURL& /*pac_url*/, + net::ProxyInfo* results) { + if (is_blocked_) + event_.Wait(); + results->UseNamedProxy(query_url.host()); + return net::OK; + } + + void Block() { + is_blocked_ = true; + event_.Reset(); + } + + void Unblock() { + is_blocked_ = false; + event_.Signal(); + } + + private: + base::WaitableEvent event_; + bool is_blocked_; +}; + +// This struct holds the values that were passed to +// Delegate::OnResolveProxyCompleted(). The caller should use WaitUntilDone() +// to block until the result has been populated. +struct ResultFuture { + public: + ResultFuture() + : reply_msg(NULL), + error_code(0), + started_(false, false), + completed_(false, false) { + } + + // Wait until the request has completed. In other words we have invoked: + // ResolveProxyMsgHelper::Delegate::OnResolveProxyCompleted. + void WaitUntilDone() { + completed_.Wait(); + } + + // Wait until the request has been sent to ResolveProxyMsgHelper. + void WaitUntilStarted() { + started_.Wait(); + } + + bool TimedWaitUntilDone(const base::TimeDelta& max_time) { + return completed_.TimedWait(max_time); + } + + // These fields are only valid after returning from WaitUntilDone(). + IPC::Message* reply_msg; + int error_code; + std::string proxy_list; + + private: + friend class AsyncRequestRunner; + base::WaitableEvent started_; + base::WaitableEvent completed_; +}; + +// This class lives on the io thread. It starts async requests using the +// class under test (ResolveProxyMsgHelper), and signals the result future on +// completion. +class AsyncRequestRunner : public ResolveProxyMsgHelper::Delegate { + public: + AsyncRequestRunner(net::ProxyService* proxy_service) { + resolve_proxy_msg_helper_.reset( + new ResolveProxyMsgHelper(this, proxy_service)); + } + + void Start(ResultFuture* future, const GURL& url, IPC::Message* reply_msg) { + futures_.push_back(future); + resolve_proxy_msg_helper_->Start(url, reply_msg); + + // Notify of request start. + future->started_.Signal(); + } + + virtual void OnResolveProxyCompleted(IPC::Message* reply_msg, + int error_code, + const std::string& proxy_list) { + // Update the result future for this request (top of queue), and signal it. + ResultFuture* future = futures_.front(); + futures_.pop_front(); + + future->reply_msg = reply_msg; + future->error_code = error_code; + future->proxy_list = proxy_list; + + // Notify of request completion. + future->completed_.Signal(); + } + + private: + std::deque<ResultFuture*> futures_; + scoped_ptr<ResolveProxyMsgHelper> resolve_proxy_msg_helper_; +}; + +// Helper class to start async requests on an io thread, and return a +// result future. The caller then uses ResultFuture::WaitUntilDone() to +// get at the results. It "bridges" the originating thread with the helper +// io thread. +class RunnerBridge { + public: + RunnerBridge() : io_thread_("io_thread"), done_(true, false) { + // Start an io thread where we will run the async requests. + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + io_thread_.StartWithOptions(options); + + // Construct the state that lives on io thread. + io_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RunnerBridge::DoConstruct)); + done_.Wait(); + } + + // Start an async request on the io thread. + ResultFuture* Start(const GURL& url, IPC::Message* reply_msg) { + ResultFuture* future = new ResultFuture(); + + io_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + async_runner_, &AsyncRequestRunner::Start, future, url, reply_msg)); + + return future; + } + + void DestroyAsyncRunner() { + io_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RunnerBridge::DoDestroyAsyncRunner)); + done_.Wait(); + } + + ~RunnerBridge() { + io_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RunnerBridge::DoDestroy)); + done_.Wait(); + } + + MockProxyResolver* proxy_resolver() { + return proxy_resolver_; + } + + // Called from io thread. + void DoConstruct() { + proxy_resolver_ = new MockProxyResolver(); + proxy_service_ = new net::ProxyService(new MockProxyConfigService(), + proxy_resolver_); + async_runner_ = new AsyncRequestRunner(proxy_service_); + done_.Signal(); + } + + // Called from io thread. + void DoDestroy() { + delete async_runner_; + delete proxy_service_; + done_.Signal(); + } + + // Called from io thread. + void DoDestroyAsyncRunner() { + delete async_runner_; + async_runner_ = NULL; + done_.Signal(); + } + + private: + base::Thread io_thread_; + base::WaitableEvent done_; + + net::ProxyService* proxy_service_; + MockProxyResolver* proxy_resolver_; // Owned by proxy_service_. + + AsyncRequestRunner* async_runner_; +}; + +// Avoid the need to have an AddRef / Release +template<> +void RunnableMethodTraits<RunnerBridge>::RetainCallee(RunnerBridge*) {} +template<> +void RunnableMethodTraits<RunnerBridge>::ReleaseCallee(RunnerBridge*) {} + +template<> +void RunnableMethodTraits<AsyncRequestRunner>::RetainCallee(AsyncRequestRunner*) {} +template<> +void RunnableMethodTraits<AsyncRequestRunner>::ReleaseCallee(AsyncRequestRunner*) {} + + +// Issue three sequential requests -- each should succeed. +TEST(ResolveProxyMsgHelperTest, Sequential) { + RunnerBridge runner; + + GURL url1("http://www.google1.com/"); + GURL url2("http://www.google2.com/"); + GURL url3("http://www.google3.com/"); + + scoped_ptr<IPC::Message> msg1(new IPC::Message()); + scoped_ptr<IPC::Message> msg2(new IPC::Message()); + scoped_ptr<IPC::Message> msg3(new IPC::Message()); + + // Execute each request sequentially (so there are never 2 requests + // outstanding at the same time). + + scoped_ptr<ResultFuture> result1(runner.Start(url1, msg1.get())); + result1->WaitUntilDone(); + + scoped_ptr<ResultFuture> result2(runner.Start(url2, msg2.get())); + result2->WaitUntilDone(); + + scoped_ptr<ResultFuture> result3(runner.Start(url3, msg3.get())); + result3->WaitUntilDone(); + + // Check that each request gave the expected result. + + EXPECT_EQ(msg1.get(), result1->reply_msg); + EXPECT_EQ(net::OK, result1->error_code); + EXPECT_EQ("PROXY www.google1.com", result1->proxy_list); + + EXPECT_EQ(msg2.get(), result2->reply_msg); + EXPECT_EQ(net::OK, result2->error_code); + EXPECT_EQ("PROXY www.google2.com", result2->proxy_list); + + EXPECT_EQ(msg3.get(), result3->reply_msg); + EXPECT_EQ(net::OK, result3->error_code); + EXPECT_EQ("PROXY www.google3.com", result3->proxy_list); +} + +// Issue a request while one is already in progress -- should be queued. +TEST(ResolveProxyMsgHelperTest, QueueRequests) { + RunnerBridge runner; + + GURL url1("http://www.google1.com/"); + GURL url2("http://www.google2.com/"); + GURL url3("http://www.google3.com/"); + + scoped_ptr<IPC::Message> msg1(new IPC::Message()); + scoped_ptr<IPC::Message> msg2(new IPC::Message()); + scoped_ptr<IPC::Message> msg3(new IPC::Message()); + + // Make the proxy resolver hang on the next request. + runner.proxy_resolver()->Block(); + + // Start three requests. Since the proxy resolver is hung, the second two + // will be pending. + + scoped_ptr<ResultFuture> result1(runner.Start(url1, msg1.get())); + scoped_ptr<ResultFuture> result2(runner.Start(url2, msg2.get())); + scoped_ptr<ResultFuture> result3(runner.Start(url3, msg3.get())); + + // Wait for the final request to have been scheduled. Otherwise we may rush + // to calling Unblock() without actually having blocked anything. + result3->WaitUntilStarted(); + + // Unblock the proxy service so requests 1-3 can complete. + runner.proxy_resolver()->Unblock(); + + // Wait for all the requests to finish (they run in FIFO order). + result3->WaitUntilDone(); + + // Check that each call invoked the callback with the right parameters. + + EXPECT_EQ(msg1.get(), result1->reply_msg); + EXPECT_EQ(net::OK, result1->error_code); + EXPECT_EQ("PROXY www.google1.com", result1->proxy_list); + + EXPECT_EQ(msg2.get(), result2->reply_msg); + EXPECT_EQ(net::OK, result2->error_code); + EXPECT_EQ("PROXY www.google2.com", result2->proxy_list); + + EXPECT_EQ(msg3.get(), result3->reply_msg); + EXPECT_EQ(net::OK, result3->error_code); + EXPECT_EQ("PROXY www.google3.com", result3->proxy_list); +} + +// Delete the helper while a request is in progress, and others are pending. +TEST(ResolveProxyMsgHelperTest, CancelPendingRequests) { + RunnerBridge runner; + + GURL url1("http://www.google1.com/"); + GURL url2("http://www.google2.com/"); + GURL url3("http://www.google3.com/"); + + // NOTE: these are not scoped ptr, since they will be deleted by the + // request's cancellation. + IPC::Message* msg1 = new IPC::Message(); + IPC::Message* msg2 = new IPC::Message(); + IPC::Message* msg3 = new IPC::Message(); + + // Make the next request block. + runner.proxy_resolver()->Block(); + + // Start three requests; since the first one blocked, the other two should + // be pending. + + scoped_ptr<ResultFuture> result1(runner.Start(url1, msg1)); + scoped_ptr<ResultFuture> result2(runner.Start(url2, msg2)); + scoped_ptr<ResultFuture> result3(runner.Start(url3, msg3)); + + result3->WaitUntilStarted(); + + // Delete the underlying ResolveProxyMsgHelper -- this should cancel all + // the requests which are outstanding. + runner.DestroyAsyncRunner(); + + // Unblocking the proxy resolver means the three requests can complete -- + // however they should not try to notify the delegate since we have already + // deleted the helper. + runner.proxy_resolver()->Unblock(); + + // Check that none of the requests were sent to the delegate. + EXPECT_FALSE( + result1->TimedWaitUntilDone(base::TimeDelta::FromMilliseconds(2))); + EXPECT_FALSE( + result2->TimedWaitUntilDone(base::TimeDelta::FromMilliseconds(2))); + EXPECT_FALSE( + result3->TimedWaitUntilDone(base::TimeDelta::FromMilliseconds(2))); + + // It should also be the case that msg1, msg2, msg3 were deleted by the + // cancellation. (Else will show up as a leak in Purify). +} + |