summaryrefslogtreecommitdiffstats
path: root/sync/internal_api/http_bridge.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sync/internal_api/http_bridge.cc')
-rw-r--r--sync/internal_api/http_bridge.cc317
1 files changed, 317 insertions, 0 deletions
diff --git a/sync/internal_api/http_bridge.cc b/sync/internal_api/http_bridge.cc
new file mode 100644
index 0000000..6aa6c4a
--- /dev/null
+++ b/sync/internal_api/http_bridge.cc
@@ -0,0 +1,317 @@
+// 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 "sync/internal_api/public/http_bridge.h"
+
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "base/string_number_conversions.h"
+#include "net/base/host_resolver.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_response_headers.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_status.h"
+
+namespace browser_sync {
+
+HttpBridge::RequestContextGetter::RequestContextGetter(
+ net::URLRequestContextGetter* baseline_context_getter,
+ const std::string& user_agent)
+ : baseline_context_getter_(baseline_context_getter),
+ network_task_runner_(
+ baseline_context_getter_->GetNetworkTaskRunner()),
+ user_agent_(user_agent) {
+ DCHECK(baseline_context_getter_);
+ DCHECK(network_task_runner_);
+ DCHECK(!user_agent_.empty());
+}
+
+HttpBridge::RequestContextGetter::~RequestContextGetter() {}
+
+net::URLRequestContext*
+HttpBridge::RequestContextGetter::GetURLRequestContext() {
+ // Lazily create the context.
+ if (!context_.get()) {
+ net::URLRequestContext* baseline_context =
+ baseline_context_getter_->GetURLRequestContext();
+ context_.reset(
+ new RequestContext(baseline_context, GetNetworkTaskRunner(),
+ user_agent_));
+ baseline_context_getter_ = NULL;
+ }
+
+ return context_.get();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+HttpBridge::RequestContextGetter::GetNetworkTaskRunner() const {
+ return network_task_runner_;
+}
+
+HttpBridgeFactory::HttpBridgeFactory(
+ net::URLRequestContextGetter* baseline_context_getter,
+ const std::string& user_agent)
+ : request_context_getter_(
+ new HttpBridge::RequestContextGetter(
+ baseline_context_getter, user_agent)) {}
+
+HttpBridgeFactory::~HttpBridgeFactory() {
+}
+
+csync::HttpPostProviderInterface* HttpBridgeFactory::Create() {
+ HttpBridge* http = new HttpBridge(request_context_getter_);
+ http->AddRef();
+ return http;
+}
+
+void HttpBridgeFactory::Destroy(csync::HttpPostProviderInterface* http) {
+ static_cast<HttpBridge*>(http)->Release();
+}
+
+HttpBridge::RequestContext::RequestContext(
+ net::URLRequestContext* baseline_context,
+ const scoped_refptr<base::SingleThreadTaskRunner>&
+ network_task_runner,
+ const std::string& user_agent)
+ : baseline_context_(baseline_context),
+ network_task_runner_(network_task_runner),
+ user_agent_(user_agent) {
+ DCHECK(!user_agent_.empty());
+
+ // Create empty, in-memory cookie store.
+ set_cookie_store(new net::CookieMonster(NULL, NULL));
+
+ // We don't use a cache for bridged loads, but we do want to share proxy info.
+ set_host_resolver(baseline_context->host_resolver());
+ set_proxy_service(baseline_context->proxy_service());
+ set_ssl_config_service(baseline_context->ssl_config_service());
+
+ // We want to share the HTTP session data with the network layer factory,
+ // which includes auth_cache for proxies.
+ // Session is not refcounted so we need to be careful to not lose the parent
+ // context.
+ net::HttpNetworkSession* session =
+ baseline_context->http_transaction_factory()->GetSession();
+ DCHECK(session);
+ set_http_transaction_factory(new net::HttpNetworkLayer(session));
+
+ // TODO(timsteele): We don't currently listen for pref changes of these
+ // fields or CookiePolicy; I'm not sure we want to strictly follow the
+ // default settings, since for example if the user chooses to block all
+ // cookies, sync will start failing. Also it seems like accept_lang/charset
+ // should be tied to whatever the sync servers expect (if anything). These
+ // fields should probably just be settable by sync backend; though we should
+ // figure out if we need to give the user explicit control over policies etc.
+ set_accept_language(baseline_context->accept_language());
+ set_accept_charset(baseline_context->accept_charset());
+
+ set_net_log(baseline_context->net_log());
+}
+
+HttpBridge::RequestContext::~RequestContext() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ delete http_transaction_factory();
+}
+
+const std::string& HttpBridge::RequestContext::GetUserAgent(
+ const GURL& url) const {
+ return user_agent_;
+}
+
+HttpBridge::URLFetchState::URLFetchState() : url_poster(NULL),
+ aborted(false),
+ request_completed(false),
+ request_succeeded(false),
+ http_response_code(-1),
+ error_code(-1) {}
+HttpBridge::URLFetchState::~URLFetchState() {}
+
+HttpBridge::HttpBridge(HttpBridge::RequestContextGetter* context_getter)
+ : context_getter_for_request_(context_getter),
+ network_task_runner_(
+ context_getter_for_request_->GetNetworkTaskRunner()),
+ created_on_loop_(MessageLoop::current()),
+ http_post_completed_(false, false) {
+}
+
+HttpBridge::~HttpBridge() {
+}
+
+void HttpBridge::SetExtraRequestHeaders(const char * headers) {
+ DCHECK(extra_headers_.empty())
+ << "HttpBridge::SetExtraRequestHeaders called twice.";
+ extra_headers_.assign(headers);
+}
+
+void HttpBridge::SetURL(const char* url, int port) {
+ DCHECK_EQ(MessageLoop::current(), created_on_loop_);
+ if (DCHECK_IS_ON()) {
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(!fetch_state_.request_completed);
+ }
+ DCHECK(url_for_request_.is_empty())
+ << "HttpBridge::SetURL called more than once?!";
+ GURL temp(url);
+ GURL::Replacements replacements;
+ std::string port_str = base::IntToString(port);
+ replacements.SetPort(port_str.c_str(),
+ url_parse::Component(0, port_str.length()));
+ url_for_request_ = temp.ReplaceComponents(replacements);
+}
+
+void HttpBridge::SetPostPayload(const char* content_type,
+ int content_length,
+ const char* content) {
+ DCHECK_EQ(MessageLoop::current(), created_on_loop_);
+ if (DCHECK_IS_ON()) {
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(!fetch_state_.request_completed);
+ }
+ DCHECK(content_type_.empty()) << "Bridge payload already set.";
+ DCHECK_GE(content_length, 0) << "Content length < 0";
+ content_type_ = content_type;
+ if (!content || (content_length == 0)) {
+ DCHECK_EQ(content_length, 0);
+ request_content_ = " "; // TODO(timsteele): URLFetcher requires non-empty
+ // content for POSTs whereas CURL does not, for now
+ // we hack this to support the sync backend.
+ } else {
+ request_content_.assign(content, content_length);
+ }
+}
+
+bool HttpBridge::MakeSynchronousPost(int* error_code, int* response_code) {
+ DCHECK_EQ(MessageLoop::current(), created_on_loop_);
+ if (DCHECK_IS_ON()) {
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(!fetch_state_.request_completed);
+ }
+ DCHECK(url_for_request_.is_valid()) << "Invalid URL for request";
+ DCHECK(!content_type_.empty()) << "Payload not set";
+
+ if (!network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpBridge::CallMakeAsynchronousPost, this))) {
+ // This usually happens when we're in a unit test.
+ LOG(WARNING) << "Could not post CallMakeAsynchronousPost task";
+ return false;
+ }
+
+ // Block until network request completes or is aborted. See
+ // OnURLFetchComplete and Abort.
+ http_post_completed_.Wait();
+
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(fetch_state_.request_completed || fetch_state_.aborted);
+ *error_code = fetch_state_.error_code;
+ *response_code = fetch_state_.http_response_code;
+ return fetch_state_.request_succeeded;
+}
+
+void HttpBridge::MakeAsynchronousPost() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(!fetch_state_.request_completed);
+ if (fetch_state_.aborted)
+ return;
+
+ fetch_state_.url_poster = net::URLFetcher::Create(
+ url_for_request_, net::URLFetcher::POST, this);
+ fetch_state_.url_poster->SetRequestContext(context_getter_for_request_);
+ fetch_state_.url_poster->SetUploadData(content_type_, request_content_);
+ fetch_state_.url_poster->SetExtraRequestHeaders(extra_headers_);
+ fetch_state_.url_poster->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES);
+ fetch_state_.url_poster->Start();
+}
+
+int HttpBridge::GetResponseContentLength() const {
+ DCHECK_EQ(MessageLoop::current(), created_on_loop_);
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(fetch_state_.request_completed);
+ return fetch_state_.response_content.size();
+}
+
+const char* HttpBridge::GetResponseContent() const {
+ DCHECK_EQ(MessageLoop::current(), created_on_loop_);
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(fetch_state_.request_completed);
+ return fetch_state_.response_content.data();
+}
+
+const std::string HttpBridge::GetResponseHeaderValue(
+ const std::string& name) const {
+
+ DCHECK_EQ(MessageLoop::current(), created_on_loop_);
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(fetch_state_.request_completed);
+
+ std::string value;
+ fetch_state_.response_headers->EnumerateHeader(NULL, name, &value);
+ return value;
+}
+
+void HttpBridge::Abort() {
+ base::AutoLock lock(fetch_state_lock_);
+ DCHECK(!fetch_state_.aborted);
+ if (fetch_state_.aborted || fetch_state_.request_completed)
+ return;
+
+ fetch_state_.aborted = true;
+ if (!network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpBridge::DestroyURLFetcherOnIOThread, this,
+ fetch_state_.url_poster))) {
+ // Madness ensues.
+ NOTREACHED() << "Could not post task to delete URLFetcher";
+ }
+
+ fetch_state_.url_poster = NULL;
+ fetch_state_.error_code = net::ERR_ABORTED;
+ http_post_completed_.Signal();
+}
+
+void HttpBridge::DestroyURLFetcherOnIOThread(net::URLFetcher* fetcher) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ delete fetcher;
+}
+
+void HttpBridge::OnURLFetchComplete(const net::URLFetcher* source) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ base::AutoLock lock(fetch_state_lock_);
+ if (fetch_state_.aborted)
+ return;
+
+ fetch_state_.request_completed = true;
+ fetch_state_.request_succeeded =
+ (net::URLRequestStatus::SUCCESS == source->GetStatus().status());
+ fetch_state_.http_response_code = source->GetResponseCode();
+ fetch_state_.error_code = source->GetStatus().error();
+
+ // Use a real (non-debug) log to facilitate troubleshooting in the wild.
+ VLOG(2) << "HttpBridge::OnURLFetchComplete for: "
+ << fetch_state_.url_poster->GetURL().spec();
+ VLOG(1) << "HttpBridge received response code: "
+ << fetch_state_.http_response_code;
+
+ source->GetResponseAsString(&fetch_state_.response_content);
+ fetch_state_.response_headers = source->GetResponseHeaders();
+
+ // End of the line for url_poster_. It lives only on the IO loop.
+ // We defer deletion because we're inside a callback from a component of the
+ // URLFetcher, so it seems most natural / "polite" to let the stack unwind.
+ MessageLoop::current()->DeleteSoon(FROM_HERE, fetch_state_.url_poster);
+ fetch_state_.url_poster = NULL;
+
+ // Wake the blocked syncer thread in MakeSynchronousPost.
+ // WARNING: DONT DO ANYTHING AFTER THIS CALL! |this| may be deleted!
+ http_post_completed_.Signal();
+}
+
+} // namespace browser_sync