OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/sync/engine/net/server_connection_manager.h" |
| 6 |
| 7 #include <errno.h> |
| 8 |
| 9 #include <ostream> |
| 10 #include <string> |
| 11 #include <vector> |
| 12 |
| 13 #include "chrome/browser/sync/engine/net/http_return.h" |
| 14 #include "chrome/browser/sync/engine/net/url_translator.h" |
| 15 #include "chrome/browser/sync/engine/syncapi.h" |
| 16 #include "chrome/browser/sync/engine/syncer.h" |
| 17 #include "chrome/browser/sync/engine/syncproto.h" |
| 18 #include "chrome/browser/sync/protocol/sync.pb.h" |
| 19 #include "chrome/browser/sync/syncable/directory_manager.h" |
| 20 #include "chrome/browser/sync/util/character_set_converters.h" |
| 21 #include "chrome/browser/sync/util/event_sys-inl.h" |
| 22 |
| 23 namespace browser_sync { |
| 24 |
| 25 using std::ostream; |
| 26 using std::string; |
| 27 using std::vector; |
| 28 |
| 29 static const char kSyncServerSyncPath[] = "/command/"; |
| 30 |
| 31 // At the /time/ path of the sync server, we expect to find a very simple |
| 32 // time of day service that we can use to synchronize the local clock with |
| 33 // server time. |
| 34 static const char kSyncServerGetTimePath[] = "/time"; |
| 35 |
| 36 static const ServerConnectionEvent shutdown_event = |
| 37 { ServerConnectionEvent::SHUTDOWN, HttpResponse::CONNECTION_UNAVAILABLE, |
| 38 false }; |
| 39 |
| 40 typedef PThreadScopedLock<PThreadMutex> MutexLock; |
| 41 |
| 42 struct ServerConnectionManager::PlatformMembers { |
| 43 explicit PlatformMembers(const string& user_agent) { } |
| 44 void Kill() { } |
| 45 void Reset() { } |
| 46 void Reset(MutexLock*) { } |
| 47 }; |
| 48 |
| 49 bool ServerConnectionManager::Post::ReadBufferResponse( |
| 50 string* buffer_out, HttpResponse* response, bool require_response) { |
| 51 if (RC_REQUEST_OK != response->response_code) { |
| 52 response->server_status = HttpResponse::SYNC_SERVER_ERROR; |
| 53 return false; |
| 54 } |
| 55 |
| 56 if (require_response && (1 > response->content_length)) |
| 57 return false; |
| 58 |
| 59 const int64 bytes_read = ReadResponse(buffer_out, response->content_length); |
| 60 if (bytes_read != response->content_length) { |
| 61 response->server_status = HttpResponse::IO_ERROR; |
| 62 return false; |
| 63 } |
| 64 return true; |
| 65 } |
| 66 |
| 67 bool ServerConnectionManager::Post::ReadDownloadResponse( |
| 68 HttpResponse* response, string* buffer_out) { |
| 69 const int64 bytes_read = ReadResponse(buffer_out, response->content_length); |
| 70 |
| 71 if (bytes_read != response->content_length) { |
| 72 LOG(ERROR) << "Mismatched content lengths, server claimed " << |
| 73 response->content_length << ", but sent " << bytes_read; |
| 74 response->server_status = HttpResponse::IO_ERROR; |
| 75 return false; |
| 76 } |
| 77 return true; |
| 78 } |
| 79 |
| 80 namespace { |
| 81 string StripTrailingSlash(const string& s) { |
| 82 int stripped_end_pos = s.size(); |
| 83 if (s.at(stripped_end_pos - 1) == '/') { |
| 84 stripped_end_pos = stripped_end_pos - 1; |
| 85 } |
| 86 |
| 87 return s.substr(0, stripped_end_pos); |
| 88 } |
| 89 } // namespace |
| 90 |
| 91 // TODO(chron): Use a GURL instead of string concatenation. |
| 92 string ServerConnectionManager::Post::MakeConnectionURL( |
| 93 const string& sync_server, const string& path, |
| 94 bool use_ssl) const { |
| 95 string connection_url = (use_ssl ? "https://" : "http://"); |
| 96 connection_url += sync_server; |
| 97 connection_url = StripTrailingSlash(connection_url); |
| 98 connection_url += path; |
| 99 |
| 100 return connection_url; |
| 101 } |
| 102 |
| 103 int ServerConnectionManager::Post::ReadResponse(string* out_buffer, |
| 104 int length) { |
| 105 int bytes_read = buffer_.length(); |
| 106 CHECK(length <= bytes_read); |
| 107 out_buffer->assign(buffer_); |
| 108 return bytes_read; |
| 109 } |
| 110 |
| 111 // A helper class that automatically notifies when the status changes: |
| 112 struct WatchServerStatus { |
| 113 WatchServerStatus(ServerConnectionManager* conn_mgr, HttpResponse* response) |
| 114 : conn_mgr_(conn_mgr), response_(response), |
| 115 reset_count_(conn_mgr->reset_count_), |
| 116 server_reachable_(conn_mgr->server_reachable_) { |
| 117 response->server_status = conn_mgr->server_status_; |
| 118 } |
| 119 ~WatchServerStatus() { |
| 120 // Don't update the status of the connection if it has been reset. |
| 121 // TODO(timsteele): Do we need this? Is this used by multiple threads? |
| 122 if (reset_count_ != conn_mgr_->reset_count_) |
| 123 return; |
| 124 if (conn_mgr_->server_status_ != response_->server_status) { |
| 125 conn_mgr_->server_status_ = response_->server_status; |
| 126 conn_mgr_->NotifyStatusChanged(); |
| 127 return; |
| 128 } |
| 129 // Notify if we've gone on or offline. |
| 130 if (server_reachable_ != conn_mgr_->server_reachable_) |
| 131 conn_mgr_->NotifyStatusChanged(); |
| 132 } |
| 133 ServerConnectionManager* const conn_mgr_; |
| 134 HttpResponse* const response_; |
| 135 // TODO(timsteele): Should this be Barrier:AtomicIncrement? |
| 136 base::subtle::AtomicWord reset_count_; |
| 137 bool server_reachable_; |
| 138 }; |
| 139 |
| 140 ServerConnectionManager::ServerConnectionManager( |
| 141 const string& server, int port, bool use_ssl, const string& user_agent, |
| 142 const string& client_id) |
| 143 : sync_server_(server), sync_server_port_(port), |
| 144 channel_(new Channel(shutdown_event)), |
| 145 server_status_(HttpResponse::NONE), server_reachable_(false), |
| 146 client_id_(client_id), use_ssl_(use_ssl), |
| 147 user_agent_(user_agent), |
| 148 platform_(new PlatformMembers(user_agent)), |
| 149 reset_count_(0), error_count_(0), |
| 150 terminate_all_io_(false), |
| 151 proto_sync_path_(kSyncServerSyncPath), |
| 152 get_time_path_(kSyncServerGetTimePath) { |
| 153 } |
| 154 |
| 155 ServerConnectionManager::~ServerConnectionManager() { |
| 156 delete channel_; |
| 157 delete platform_; |
| 158 shutdown_event_mutex_.Lock(); |
| 159 int result = pthread_cond_broadcast(&shutdown_event_condition_.condvar_); |
| 160 shutdown_event_mutex_.Unlock(); |
| 161 if (result) { |
| 162 LOG(ERROR) << "Error signaling shutdown_event_condition_ last error = " |
| 163 << result; |
| 164 } |
| 165 } |
| 166 |
| 167 void ServerConnectionManager::NotifyStatusChanged() { |
| 168 ServerConnectionEvent event = { ServerConnectionEvent::STATUS_CHANGED, |
| 169 server_status_, |
| 170 server_reachable_ }; |
| 171 channel_->NotifyListeners(event); |
| 172 } |
| 173 |
| 174 // Uses currently set auth token. Set by AuthWatcher. |
| 175 bool ServerConnectionManager::PostBufferWithCachedAuth( |
| 176 const PostBufferParams* params) { |
| 177 string path = |
| 178 MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_)); |
| 179 return PostBufferToPath(params, path, auth_token_); |
| 180 } |
| 181 |
| 182 bool ServerConnectionManager::PostBufferWithAuth(const PostBufferParams* params, |
| 183 const string& auth_token) { |
| 184 string path = MakeSyncServerPath(proto_sync_path(), |
| 185 MakeSyncQueryString(client_id_)); |
| 186 |
| 187 return PostBufferToPath(params, path, auth_token); |
| 188 } |
| 189 |
| 190 bool ServerConnectionManager::PostBufferToPath(const PostBufferParams* params, |
| 191 const string& path, |
| 192 const string& auth_token) { |
| 193 WatchServerStatus watcher(this, params->response); |
| 194 scoped_ptr<Post> post(MakePost()); |
| 195 post->set_timing_info(params->timing_info); |
| 196 bool ok = post->Init(path.c_str(), auth_token, params->buffer_in, |
| 197 params->response); |
| 198 |
| 199 if (!ok || RC_REQUEST_OK != params->response->response_code) { |
| 200 IncrementErrorCount(); |
| 201 return false; |
| 202 } |
| 203 |
| 204 if (post->ReadBufferResponse(params->buffer_out, params->response, true)) { |
| 205 params->response->server_status = HttpResponse::SERVER_CONNECTION_OK; |
| 206 server_reachable_ = true; |
| 207 return true; |
| 208 } |
| 209 return false; |
| 210 } |
| 211 |
| 212 bool ServerConnectionManager::CheckTime(int32* out_time) { |
| 213 // Verify that the server really is reachable by checking the time. We need |
| 214 // to do this because of wifi interstitials that intercept messages from the |
| 215 // client and return HTTP OK instead of a redirect. |
| 216 HttpResponse response; |
| 217 WatchServerStatus watcher(this, &response); |
| 218 string post_body = "command=get_time"; |
| 219 |
| 220 // We only retry the CheckTime call if we were reset during the CheckTime |
| 221 // attempt. We only try 3 times in case we're in a reset loop elsewhere. |
| 222 base::subtle::AtomicWord start_reset_count = reset_count_ - 1; |
| 223 for (int i = 0 ; i < 3 && start_reset_count != reset_count_ ; i++) { |
| 224 start_reset_count = reset_count_; |
| 225 scoped_ptr<Post> post(MakePost()); |
| 226 |
| 227 // Note that the server's get_time path doesn't require authentication. |
| 228 string get_time_path = |
| 229 MakeSyncServerPath(kSyncServerGetTimePath, post_body); |
| 230 LOG(INFO) << "Requesting get_time from:" << get_time_path; |
| 231 |
| 232 string blank_post_body; |
| 233 bool ok = post->Init(get_time_path.c_str(), blank_post_body, |
| 234 blank_post_body, &response); |
| 235 if (!ok) { |
| 236 LOG(INFO) << "Unable to check the time"; |
| 237 continue; |
| 238 } |
| 239 string time_response; |
| 240 time_response.resize(response.content_length); |
| 241 ok = post->ReadDownloadResponse(&response, &time_response); |
| 242 if (!ok || string::npos != |
| 243 time_response.find_first_not_of("0123456789")) { |
| 244 LOG(ERROR) << "unable to read a non-numeric response from get_time:" |
| 245 << time_response; |
| 246 continue; |
| 247 } |
| 248 *out_time = atoi(time_response.c_str()); |
| 249 LOG(INFO) << "Server was reachable."; |
| 250 return true; |
| 251 } |
| 252 IncrementErrorCount(); |
| 253 return false; |
| 254 } |
| 255 |
| 256 bool ServerConnectionManager::IsServerReachable() { |
| 257 int32 time; |
| 258 return CheckTime(&time); |
| 259 } |
| 260 |
| 261 bool ServerConnectionManager::IsUserAuthenticated() { |
| 262 return IsGoodReplyFromServer(server_status_); |
| 263 } |
| 264 |
| 265 bool ServerConnectionManager::CheckServerReachable() { |
| 266 const bool server_is_reachable = IsServerReachable(); |
| 267 if (server_reachable_ != server_is_reachable) { |
| 268 server_reachable_ = server_is_reachable; |
| 269 NotifyStatusChanged(); |
| 270 } |
| 271 return server_is_reachable; |
| 272 } |
| 273 |
| 274 void ServerConnectionManager::kill() { |
| 275 { |
| 276 MutexLock lock(&terminate_all_io_mutex_); |
| 277 terminate_all_io_ = true; |
| 278 } |
| 279 platform_->Kill(); |
| 280 shutdown_event_mutex_.Lock(); |
| 281 int result = pthread_cond_broadcast(&shutdown_event_condition_.condvar_); |
| 282 shutdown_event_mutex_.Unlock(); |
| 283 if (result) { |
| 284 LOG(ERROR) << "Error signaling shutdown_event_condition_ last error = " |
| 285 << result; |
| 286 } |
| 287 } |
| 288 |
| 289 void ServerConnectionManager::ResetAuthStatus() { |
| 290 ResetConnection(); |
| 291 server_status_ = HttpResponse::NONE; |
| 292 NotifyStatusChanged(); |
| 293 } |
| 294 |
| 295 void ServerConnectionManager::ResetConnection() { |
| 296 base::subtle::NoBarrier_AtomicIncrement(&reset_count_, 1); |
| 297 platform_->Reset(); |
| 298 } |
| 299 |
| 300 bool ServerConnectionManager::IncrementErrorCount() { |
| 301 #ifdef OS_WINDOWS |
| 302 error_count_mutex_.Lock(); |
| 303 error_count_++; |
| 304 |
| 305 if (error_count_ > kMaxConnectionErrorsBeforeReset) { |
| 306 error_count_ = 0; |
| 307 |
| 308 // Be careful with this mutex because calling out to other methods can |
| 309 // result in being called back. Unlock it here to prevent any potential |
| 310 // double-acquisitions. |
| 311 error_count_mutex_.Unlock(); |
| 312 |
| 313 if (!IsServerReachable()) { |
| 314 LOG(WARNING) << "Too many connection failures, server is not reachable. " |
| 315 << "Resetting connections."; |
| 316 ResetConnection(); |
| 317 } else { |
| 318 LOG(WARNING) << "Multiple connection failures while server is reachable."; |
| 319 } |
| 320 return false; |
| 321 } |
| 322 |
| 323 error_count_mutex_.Unlock(); |
| 324 return true; |
| 325 #endif |
| 326 return true; |
| 327 } |
| 328 |
| 329 void ServerConnectionManager::SetServerParameters(const string& server_url, |
| 330 int port, bool use_ssl) { |
| 331 { |
| 332 ParametersLock lock(&server_parameters_mutex_); |
| 333 sync_server_ = server_url; |
| 334 sync_server_port_ = port; |
| 335 use_ssl_ = use_ssl; |
| 336 } |
| 337 platform_->Reset(); |
| 338 } |
| 339 |
| 340 // Returns the current server parameters in server_url and port. |
| 341 void ServerConnectionManager::GetServerParameters(string* server_url, |
| 342 int* port, bool* use_ssl) { |
| 343 ParametersLock lock(&server_parameters_mutex_); |
| 344 if (server_url != NULL) |
| 345 *server_url = sync_server_; |
| 346 if (port != NULL) |
| 347 *port = sync_server_port_; |
| 348 if (use_ssl != NULL) |
| 349 *use_ssl = use_ssl_; |
| 350 } |
| 351 |
| 352 bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm, |
| 353 syncable::DirectoryManager* manager, |
| 354 const PathString &share) { |
| 355 syncable::ScopedDirLookup dir(manager, share); |
| 356 if (!dir.good()) { |
| 357 LOG(INFO) << "Dir lookup failed"; |
| 358 return false; |
| 359 } |
| 360 string birthday = dir->store_birthday(); |
| 361 if (!birthday.empty()) |
| 362 csm->set_store_birthday(birthday); |
| 363 csm->set_share(ToUTF8(share).get_string()); |
| 364 return true; |
| 365 } |
| 366 |
| 367 } // namespace browser_sync |
| 368 |
| 369 std::ostream& operator << (std::ostream& s, |
| 370 const struct browser_sync::HttpResponse& hr) { |
| 371 s << " Response Code (bogus on error): " << hr.response_code; |
| 372 s << " Content-Length (bogus on error): " << hr.content_length; |
| 373 s << " Server Status: " << hr.server_status; |
| 374 return s; |
| 375 } |
OLD | NEW |