Chromium Code Reviews| Index: chrome/browser/sync/engine/net/server_connection_manager.cc |
| =================================================================== |
| --- chrome/browser/sync/engine/net/server_connection_manager.cc (revision 0) |
| +++ chrome/browser/sync/engine/net/server_connection_manager.cc (revision 0) |
| @@ -0,0 +1,375 @@ |
| +// 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/sync/engine/net/server_connection_manager.h" |
| + |
| +#include <errno.h> |
| + |
| +#include <ostream> |
| +#include <string> |
| +#include <vector> |
| + |
| +#include "chrome/browser/sync/engine/net/http_return.h" |
| +#include "chrome/browser/sync/engine/net/url_translator.h" |
| +#include "chrome/browser/sync/engine/syncapi.h" |
| +#include "chrome/browser/sync/engine/syncer.h" |
| +#include "chrome/browser/sync/engine/syncproto.h" |
| +#include "chrome/browser/sync/protocol/sync.pb.h" |
| +#include "chrome/browser/sync/syncable/directory_manager.h" |
| +#include "chrome/browser/sync/util/character_set_converters.h" |
| +#include "chrome/browser/sync/util/event_sys-inl.h" |
| + |
| +namespace browser_sync { |
| + |
| +using std::ostream; |
| +using std::string; |
| +using std::vector; |
| + |
| +static const char kSyncServerSyncPath[] = "/command/"; |
| + |
| +// At the /time/ path of the sync server, we expect to find a very simple |
| +// time of day service that we can use to synchronize the local clock with |
| +// server time. |
| +static const char kSyncServerGetTimePath[] = "/time"; |
| + |
| +static const ServerConnectionEvent shutdown_event = |
| + { ServerConnectionEvent::SHUTDOWN, HttpResponse::CONNECTION_UNAVAILABLE, |
| + false }; |
| + |
| +typedef PThreadScopedLock<PThreadMutex> MutexLock; |
| + |
| +struct ServerConnectionManager::PlatformMembers { |
| + explicit PlatformMembers(const string& user_agent) { } |
| + void Kill() { } |
| + void Reset() { } |
| + void Reset(MutexLock*) { } |
| +}; |
| + |
| +bool ServerConnectionManager::Post::ReadBufferResponse( |
| + string* buffer_out, HttpResponse* response, bool require_response) { |
| + if (RC_REQUEST_OK != response->response_code) { |
| + response->server_status = HttpResponse::SYNC_SERVER_ERROR; |
| + return false; |
| + } |
| + |
| + if (require_response && (1 > response->content_length)) |
| + return false; |
| + |
| + const int64 bytes_read = ReadResponse(buffer_out, response->content_length); |
| + if (bytes_read != response->content_length) { |
| + response->server_status = HttpResponse::IO_ERROR; |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +bool ServerConnectionManager::Post::ReadDownloadResponse( |
| + HttpResponse* response, string* buffer_out) { |
| + const int64 bytes_read = ReadResponse(buffer_out, response->content_length); |
| + |
| + if (bytes_read != response->content_length) { |
| + LOG(ERROR) << "Mismatched content lengths, server claimed " << |
| + response->content_length << ", but sent " << bytes_read; |
| + response->server_status = HttpResponse::IO_ERROR; |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +namespace { |
| + string StripTrailingSlash(const string& s) { |
| + int stripped_end_pos = s.size(); |
| + if (s.at(stripped_end_pos - 1) == '/') { |
| + stripped_end_pos = stripped_end_pos - 1; |
| + } |
| + |
| + return s.substr(0, stripped_end_pos); |
| + } |
| +} // namespace |
| + |
| +// TODO(chron): Use a GURL instead of string concatenation. |
| + string ServerConnectionManager::Post::MakeConnectionURL( |
|
idana
2009/09/10 05:44:37
Indentation
|
| + const string& sync_server, const string& path, |
| + bool use_ssl) const { |
| + string connection_url = (use_ssl ? "https://" : "http://"); |
| + connection_url += sync_server; |
| + connection_url = StripTrailingSlash(connection_url); |
| + connection_url += path; |
| + |
| + return connection_url; |
| +} |
| + |
| +int ServerConnectionManager::Post::ReadResponse(string* out_buffer, |
| + int length) { |
| + int bytes_read = buffer_.length(); |
| + CHECK(length <= bytes_read); |
| + out_buffer->assign(buffer_); |
| + return bytes_read; |
| +} |
| + |
| +// A helper class that automatically notifies when the status changes: |
| +struct WatchServerStatus { |
| + WatchServerStatus(ServerConnectionManager* conn_mgr, HttpResponse* response) |
| + : conn_mgr_(conn_mgr), response_(response), |
| + reset_count_(conn_mgr->reset_count_), |
| + server_reachable_(conn_mgr->server_reachable_) { |
| + response->server_status = conn_mgr->server_status_; |
| + } |
| + ~WatchServerStatus() { |
| + // Don't update the status of the connection if it has been reset. |
| + // TODO(timsteele): Do we need this? Is this used by multiple threads? |
| + if (reset_count_ != conn_mgr_->reset_count_) |
| + return; |
| + if (conn_mgr_->server_status_ != response_->server_status) { |
| + conn_mgr_->server_status_ = response_->server_status; |
| + conn_mgr_->NotifyStatusChanged(); |
| + return; |
| + } |
| + // Notify if we've gone on or offline. |
| + if (server_reachable_ != conn_mgr_->server_reachable_) |
| + conn_mgr_->NotifyStatusChanged(); |
| + } |
| + ServerConnectionManager* const conn_mgr_; |
| + HttpResponse* const response_; |
| + // TODO(timsteele): Should this be Barrier:AtomicIncrement? |
| + base::subtle::AtomicWord reset_count_; |
| + bool server_reachable_; |
| +}; |
| + |
| +ServerConnectionManager::ServerConnectionManager( |
| + const string& server, int port, bool use_ssl, const string& user_agent, |
| + const string& client_id) |
| + : sync_server_(server), sync_server_port_(port), |
| + channel_(new Channel(shutdown_event)), |
| + server_status_(HttpResponse::NONE), server_reachable_(false), |
| + client_id_(client_id), use_ssl_(use_ssl), |
| + user_agent_(user_agent), |
| + platform_(new PlatformMembers(user_agent)), |
| + reset_count_(0), error_count_(0), |
| + terminate_all_io_(false), |
| + proto_sync_path_(kSyncServerSyncPath), |
| + get_time_path_(kSyncServerGetTimePath) { |
| +} |
| + |
| +ServerConnectionManager::~ServerConnectionManager() { |
| + delete channel_; |
| + delete platform_; |
| + shutdown_event_mutex_.Lock(); |
| + int result = pthread_cond_broadcast(&shutdown_event_condition_.condvar_); |
| + shutdown_event_mutex_.Unlock(); |
| + if (result) { |
| + LOG(ERROR) << "Error signaling shutdown_event_condition_ last error = " |
| + << result; |
| + } |
| +} |
| + |
| +void ServerConnectionManager::NotifyStatusChanged() { |
| + ServerConnectionEvent event = { ServerConnectionEvent::STATUS_CHANGED, |
| + server_status_, |
| + server_reachable_ }; |
| + channel_->NotifyListeners(event); |
| +} |
| + |
| +// Uses currently set auth token. Set by AuthWatcher. |
| +bool ServerConnectionManager::PostBufferWithCachedAuth( |
| + const PostBufferParams* params) { |
| + string path = |
| + MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_)); |
| + return PostBufferToPath(params, path, auth_token_); |
| +} |
| + |
| +bool ServerConnectionManager::PostBufferWithAuth(const PostBufferParams* params, |
| + const string& auth_token) { |
| + string path = MakeSyncServerPath(proto_sync_path(), |
| + MakeSyncQueryString(client_id_)); |
| + |
| + return PostBufferToPath(params, path, auth_token); |
| +} |
| + |
| +bool ServerConnectionManager::PostBufferToPath(const PostBufferParams* params, |
| + const string& path, |
| + const string& auth_token) { |
| + WatchServerStatus watcher(this, params->response); |
| + scoped_ptr<Post> post(MakePost()); |
| + post->set_timing_info(params->timing_info); |
| + bool ok = post->Init(path.c_str(), auth_token, params->buffer_in, |
| + params->response); |
| + |
| + if (!ok || RC_REQUEST_OK != params->response->response_code) { |
| + IncrementErrorCount(); |
| + return false; |
| + } |
| + |
| + if (post->ReadBufferResponse(params->buffer_out, params->response, true)) { |
| + params->response->server_status = HttpResponse::SERVER_CONNECTION_OK; |
| + server_reachable_ = true; |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +bool ServerConnectionManager::CheckTime(int32* out_time) { |
| + // Verify that the server really is reachable by checking the time. We need |
| + // to do this because of wifi interstitials that intercept messages from the |
| + // client and return HTTP OK instead of a redirect. |
| + HttpResponse response; |
| + WatchServerStatus watcher(this, &response); |
| + string post_body = "command=get_time"; |
| + |
| + // We only retry the CheckTime call if we were reset during the CheckTime |
| + // attempt. We only try 3 times in case we're in a reset loop elsewhere. |
| + base::subtle::AtomicWord start_reset_count = reset_count_ - 1; |
| + for (int i = 0 ; i < 3 && start_reset_count != reset_count_ ; i++) { |
| + start_reset_count = reset_count_; |
| + scoped_ptr<Post> post(MakePost()); |
| + |
| + // Note that the server's get_time path doesn't require authentication. |
| + string get_time_path = |
| + MakeSyncServerPath(kSyncServerGetTimePath, post_body); |
| + LOG(INFO) << "Requesting get_time from:" << get_time_path; |
| + |
| + string blank_post_body; |
| + bool ok = post->Init(get_time_path.c_str(), blank_post_body, |
| + blank_post_body, &response); |
| + if (!ok) { |
| + LOG(INFO) << "Unable to check the time"; |
| + continue; |
| + } |
| + string time_response; |
| + time_response.resize(response.content_length); |
| + ok = post->ReadDownloadResponse(&response, &time_response); |
| + if (!ok || string::npos != |
| + time_response.find_first_not_of("0123456789")) { |
| + LOG(ERROR) << "unable to read a non-numeric response from get_time:" |
| + << time_response; |
| + continue; |
| + } |
| + *out_time = atoi(time_response.c_str()); |
| + LOG(INFO) << "Server was reachable."; |
| + return true; |
| + } |
| + IncrementErrorCount(); |
| + return false; |
| +} |
| + |
| +bool ServerConnectionManager::IsServerReachable() { |
| + int32 time; |
| + return CheckTime(&time); |
| +} |
| + |
| +bool ServerConnectionManager::IsUserAuthenticated() { |
| + return IsGoodReplyFromServer(server_status_); |
| +} |
| + |
| +bool ServerConnectionManager::CheckServerReachable() { |
| + const bool server_is_reachable = IsServerReachable(); |
| + if (server_reachable_ != server_is_reachable) { |
| + server_reachable_ = server_is_reachable; |
| + NotifyStatusChanged(); |
| + } |
| + return server_is_reachable; |
| +} |
| + |
| +void ServerConnectionManager::kill() { |
| + { |
| + MutexLock lock(&terminate_all_io_mutex_); |
| + terminate_all_io_ = true; |
| + } |
| + platform_->Kill(); |
| + shutdown_event_mutex_.Lock(); |
| + int result = pthread_cond_broadcast(&shutdown_event_condition_.condvar_); |
| + shutdown_event_mutex_.Unlock(); |
| + if (result) { |
| + LOG(ERROR) << "Error signaling shutdown_event_condition_ last error = " |
| + << result; |
| + } |
| +} |
| + |
| +void ServerConnectionManager::ResetAuthStatus() { |
| + ResetConnection(); |
| + server_status_ = HttpResponse::NONE; |
| + NotifyStatusChanged(); |
| +} |
| + |
| +void ServerConnectionManager::ResetConnection() { |
| + base::subtle::NoBarrier_AtomicIncrement(&reset_count_, 1); |
| + platform_->Reset(); |
| +} |
| + |
| +bool ServerConnectionManager::IncrementErrorCount() { |
| +#ifdef OS_WINDOWS |
| + error_count_mutex_.Lock(); |
| + error_count_++; |
| + |
| + if (error_count_ > kMaxConnectionErrorsBeforeReset) { |
| + error_count_ = 0; |
| + |
| + // Be careful with this mutex because calling out to other methods can |
| + // result in being called back. Unlock it here to prevent any potential |
| + // double-acquisitions. |
| + error_count_mutex_.Unlock(); |
| + |
| + if (!IsServerReachable()) { |
| + LOG(WARNING) << "Too many connection failures, server is not reachable. " |
| + << "Resetting connections."; |
| + ResetConnection(); |
| + } else { |
| + LOG(WARNING) << "Multiple connection failures while server is reachable."; |
| + } |
| + return false; |
| + } |
| + |
| + error_count_mutex_.Unlock(); |
| + return true; |
| +#endif |
| + return true; |
| +} |
| + |
| +void ServerConnectionManager::SetServerParameters(const string& server_url, |
| + int port, bool use_ssl) { |
| + { |
| + ParametersLock lock(&server_parameters_mutex_); |
| + sync_server_ = server_url; |
| + sync_server_port_ = port; |
| + use_ssl_ = use_ssl; |
| + } |
| + platform_->Reset(); |
| +} |
| + |
| +// Returns the current server parameters in server_url and port. |
| +void ServerConnectionManager::GetServerParameters(string* server_url, |
| + int* port, bool* use_ssl) { |
| + ParametersLock lock(&server_parameters_mutex_); |
| + if (server_url != NULL) |
| + *server_url = sync_server_; |
| + if (port != NULL) |
| + *port = sync_server_port_; |
| + if (use_ssl != NULL) |
| + *use_ssl = use_ssl_; |
| +} |
| + |
| +bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm, |
| + syncable::DirectoryManager* manager, |
| + const PathString &share) { |
| + syncable::ScopedDirLookup dir(manager, share); |
| + if (!dir.good()) { |
| + LOG(INFO) << "Dir lookup failed"; |
| + return false; |
| + } |
| + string birthday = dir->store_birthday(); |
| + if (!birthday.empty()) |
| + csm->set_store_birthday(birthday); |
| + csm->set_share(ToUTF8(share).get_string()); |
| + return true; |
| +} |
| + |
| +} // namespace browser_sync |
| + |
| +std::ostream& operator << (std::ostream& s, |
| + const struct browser_sync::HttpResponse& hr) { |
| + s << " Response Code (bogus on error): " << hr.response_code; |
| + s << " Content-Length (bogus on error): " << hr.content_length; |
| + s << " Server Status: " << hr.server_status; |
| + return s; |
| +} |
| Property changes on: chrome\browser\sync\engine\net\server_connection_manager.cc |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |