server_connection_manager.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 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 "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 "base/command_line.h" 14#include "build/build_config.h" 15#include "googleurl/src/gurl.h" 16#include "net/base/net_errors.h" 17#include "net/http/http_status_code.h" 18#include "sync/engine/net/url_translator.h" 19#include "sync/engine/syncer.h" 20#include "sync/protocol/sync.pb.h" 21#include "sync/syncable/directory.h" 22 23namespace syncer { 24 25using std::ostream; 26using std::string; 27using std::vector; 28 29static const char kSyncServerSyncPath[] = "/command/"; 30 31HttpResponse::HttpResponse() 32 : response_code(kUnsetResponseCode), 33 content_length(kUnsetContentLength), 34 payload_length(kUnsetPayloadLength), 35 server_status(NONE) {} 36 37#define ENUM_CASE(x) case x: return #x; break 38 39const char* HttpResponse::GetServerConnectionCodeString( 40 ServerConnectionCode code) { 41 switch (code) { 42 ENUM_CASE(NONE); 43 ENUM_CASE(CONNECTION_UNAVAILABLE); 44 ENUM_CASE(IO_ERROR); 45 ENUM_CASE(SYNC_SERVER_ERROR); 46 ENUM_CASE(SYNC_AUTH_ERROR); 47 ENUM_CASE(SERVER_CONNECTION_OK); 48 ENUM_CASE(RETRY); 49 } 50 NOTREACHED(); 51 return ""; 52} 53 54#undef ENUM_CASE 55 56// TODO(clamy): check if all errors are in the right category. 57HttpResponse::ServerConnectionCode 58HttpResponse::ServerConnectionCodeFromNetError(int error_code) { 59 switch (error_code) { 60 case net::ERR_ABORTED: 61 case net::ERR_SOCKET_NOT_CONNECTED: 62 case net::ERR_NETWORK_CHANGED: 63 case net::ERR_CONNECTION_FAILED: 64 case net::ERR_NAME_NOT_RESOLVED: 65 case net::ERR_INTERNET_DISCONNECTED: 66 case net::ERR_NETWORK_ACCESS_DENIED: 67 case net::ERR_NETWORK_IO_SUSPENDED: 68 return CONNECTION_UNAVAILABLE; 69 } 70 return IO_ERROR; 71} 72 73ServerConnectionManager::Connection::Connection( 74 ServerConnectionManager* scm) : scm_(scm) { 75} 76 77ServerConnectionManager::Connection::~Connection() { 78} 79 80bool ServerConnectionManager::Connection::ReadBufferResponse( 81 string* buffer_out, 82 HttpResponse* response, 83 bool require_response) { 84 if (net::HTTP_OK != response->response_code) { 85 response->server_status = HttpResponse::SYNC_SERVER_ERROR; 86 return false; 87 } 88 89 if (require_response && (1 > response->content_length)) 90 return false; 91 92 const int64 bytes_read = ReadResponse(buffer_out, 93 static_cast<int>(response->content_length)); 94 if (bytes_read != response->content_length) { 95 response->server_status = HttpResponse::IO_ERROR; 96 return false; 97 } 98 return true; 99} 100 101bool ServerConnectionManager::Connection::ReadDownloadResponse( 102 HttpResponse* response, 103 string* buffer_out) { 104 const int64 bytes_read = ReadResponse(buffer_out, 105 static_cast<int>(response->content_length)); 106 107 if (bytes_read != response->content_length) { 108 LOG(ERROR) << "Mismatched content lengths, server claimed " << 109 response->content_length << ", but sent " << bytes_read; 110 response->server_status = HttpResponse::IO_ERROR; 111 return false; 112 } 113 return true; 114} 115 116ServerConnectionManager::ScopedConnectionHelper::ScopedConnectionHelper( 117 ServerConnectionManager* manager, Connection* connection) 118 : manager_(manager), connection_(connection) {} 119 120ServerConnectionManager::ScopedConnectionHelper::~ScopedConnectionHelper() { 121 if (connection_.get()) 122 manager_->OnConnectionDestroyed(connection_.get()); 123 connection_.reset(); 124} 125 126ServerConnectionManager::Connection* 127ServerConnectionManager::ScopedConnectionHelper::get() { 128 return connection_.get(); 129} 130 131namespace { 132 133string StripTrailingSlash(const string& s) { 134 int stripped_end_pos = s.size(); 135 if (s.at(stripped_end_pos - 1) == '/') { 136 stripped_end_pos = stripped_end_pos - 1; 137 } 138 139 return s.substr(0, stripped_end_pos); 140} 141 142} // namespace 143 144// TODO(chron): Use a GURL instead of string concatenation. 145string ServerConnectionManager::Connection::MakeConnectionURL( 146 const string& sync_server, 147 const string& path, 148 bool use_ssl) const { 149 string connection_url = (use_ssl ? "https://" : "http://"); 150 connection_url += sync_server; 151 connection_url = StripTrailingSlash(connection_url); 152 connection_url += path; 153 154 return connection_url; 155} 156 157int ServerConnectionManager::Connection::ReadResponse(string* out_buffer, 158 int length) { 159 int bytes_read = buffer_.length(); 160 CHECK(length <= bytes_read); 161 out_buffer->assign(buffer_); 162 return bytes_read; 163} 164 165ScopedServerStatusWatcher::ScopedServerStatusWatcher( 166 ServerConnectionManager* conn_mgr, HttpResponse* response) 167 : conn_mgr_(conn_mgr), 168 response_(response) { 169 response->server_status = conn_mgr->server_status_; 170} 171 172ScopedServerStatusWatcher::~ScopedServerStatusWatcher() { 173 conn_mgr_->SetServerStatus(response_->server_status); 174} 175 176ServerConnectionManager::ServerConnectionManager( 177 const string& server, 178 int port, 179 bool use_ssl) 180 : sync_server_(server), 181 sync_server_port_(port), 182 use_ssl_(use_ssl), 183 proto_sync_path_(kSyncServerSyncPath), 184 server_status_(HttpResponse::NONE), 185 terminated_(false), 186 active_connection_(NULL) { 187} 188 189ServerConnectionManager::~ServerConnectionManager() { 190} 191 192ServerConnectionManager::Connection* 193ServerConnectionManager::MakeActiveConnection() { 194 base::AutoLock lock(terminate_connection_lock_); 195 DCHECK(!active_connection_); 196 if (terminated_) 197 return NULL; 198 199 active_connection_ = MakeConnection(); 200 return active_connection_; 201} 202 203void ServerConnectionManager::OnConnectionDestroyed(Connection* connection) { 204 DCHECK(connection); 205 base::AutoLock lock(terminate_connection_lock_); 206 // |active_connection_| can be NULL already if it was aborted. Also, 207 // it can legitimately be a different Connection object if a new Connection 208 // was created after a previous one was Aborted and destroyed. 209 if (active_connection_ != connection) 210 return; 211 212 active_connection_ = NULL; 213} 214 215void ServerConnectionManager::OnInvalidationCredentialsRejected() { 216 InvalidateAndClearAuthToken(); 217 SetServerStatus(HttpResponse::SYNC_AUTH_ERROR); 218} 219 220void ServerConnectionManager::InvalidateAndClearAuthToken() { 221 DCHECK(thread_checker_.CalledOnValidThread()); 222 // Copy over the token to previous invalid token. 223 if (!auth_token_.empty()) { 224 previously_invalidated_token.assign(auth_token_); 225 auth_token_ = std::string(); 226 } 227} 228 229void ServerConnectionManager::SetServerStatus( 230 HttpResponse::ServerConnectionCode server_status) { 231 if (server_status_ == server_status) 232 return; 233 server_status_ = server_status; 234 NotifyStatusChanged(); 235} 236 237void ServerConnectionManager::NotifyStatusChanged() { 238 DCHECK(thread_checker_.CalledOnValidThread()); 239 FOR_EACH_OBSERVER(ServerConnectionEventListener, listeners_, 240 OnServerConnectionEvent( 241 ServerConnectionEvent(server_status_))); 242} 243 244bool ServerConnectionManager::PostBufferWithCachedAuth( 245 PostBufferParams* params, ScopedServerStatusWatcher* watcher) { 246 DCHECK(thread_checker_.CalledOnValidThread()); 247 string path = 248 MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_)); 249 return PostBufferToPath(params, path, auth_token(), watcher); 250} 251 252bool ServerConnectionManager::PostBufferToPath(PostBufferParams* params, 253 const string& path, const string& auth_token, 254 ScopedServerStatusWatcher* watcher) { 255 DCHECK(thread_checker_.CalledOnValidThread()); 256 DCHECK(watcher != NULL); 257 258 if (auth_token.empty()) { 259 params->response.server_status = HttpResponse::SYNC_AUTH_ERROR; 260 return false; 261 } 262 263 // When our connection object falls out of scope, it clears itself from 264 // active_connection_. 265 ScopedConnectionHelper post(this, MakeActiveConnection()); 266 if (!post.get()) { 267 params->response.server_status = HttpResponse::CONNECTION_UNAVAILABLE; 268 return false; 269 } 270 271 // Note that |post| may be aborted by now, which will just cause Init to fail 272 // with CONNECTION_UNAVAILABLE. 273 bool ok = post.get()->Init( 274 path.c_str(), auth_token, params->buffer_in, ¶ms->response); 275 276 if (params->response.server_status == HttpResponse::SYNC_AUTH_ERROR) 277 InvalidateAndClearAuthToken(); 278 279 if (!ok || net::HTTP_OK != params->response.response_code) 280 return false; 281 282 if (post.get()->ReadBufferResponse( 283 ¶ms->buffer_out, ¶ms->response, true)) { 284 params->response.server_status = HttpResponse::SERVER_CONNECTION_OK; 285 return true; 286 } 287 return false; 288} 289 290// Returns the current server parameters in server_url and port. 291void ServerConnectionManager::GetServerParameters(string* server_url, 292 int* port, 293 bool* use_ssl) const { 294 if (server_url != NULL) 295 *server_url = sync_server_; 296 if (port != NULL) 297 *port = sync_server_port_; 298 if (use_ssl != NULL) 299 *use_ssl = use_ssl_; 300} 301 302std::string ServerConnectionManager::GetServerHost() const { 303 string server_url; 304 int port; 305 bool use_ssl; 306 GetServerParameters(&server_url, &port, &use_ssl); 307 // For unit tests. 308 if (server_url.empty()) 309 return std::string(); 310 // We just want the hostname, so we don't need to switch on use_ssl. 311 server_url = "http://" + server_url; 312 GURL gurl(server_url); 313 DCHECK(gurl.is_valid()) << gurl; 314 return gurl.host(); 315} 316 317void ServerConnectionManager::AddListener( 318 ServerConnectionEventListener* listener) { 319 DCHECK(thread_checker_.CalledOnValidThread()); 320 listeners_.AddObserver(listener); 321} 322 323void ServerConnectionManager::RemoveListener( 324 ServerConnectionEventListener* listener) { 325 DCHECK(thread_checker_.CalledOnValidThread()); 326 listeners_.RemoveObserver(listener); 327} 328 329ServerConnectionManager::Connection* ServerConnectionManager::MakeConnection() 330{ 331 return NULL; // For testing. 332} 333 334void ServerConnectionManager::TerminateAllIO() { 335 base::AutoLock lock(terminate_connection_lock_); 336 terminated_ = true; 337 if (active_connection_) 338 active_connection_->Abort(); 339 340 // Sever our ties to this connection object. Note that it still may exist, 341 // since we don't own it, but it has been neutered. 342 active_connection_ = NULL; 343} 344 345std::ostream& operator << (std::ostream& s, const struct HttpResponse& hr) { 346 s << " Response Code (bogus on error): " << hr.response_code; 347 s << " Content-Length (bogus on error): " << hr.content_length; 348 s << " Server Status: " 349 << HttpResponse::GetServerConnectionCodeString(hr.server_status); 350 return s; 351} 352 353} // namespace syncer 354