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.
5 #include "sync/engine/net/server_connection_manager.h"
13 #include "base/metrics/histogram.h"
14 #include "build/build_config.h"
15 #include "net/base/net_errors.h"
16 #include "net/http/http_status_code.h"
17 #include "sync/engine/net/url_translator.h"
18 #include "sync/engine/syncer.h"
19 #include "sync/internal_api/public/base/cancelation_signal.h"
20 #include "sync/protocol/sync.pb.h"
21 #include "sync/syncable/directory.h"
30 static const char kSyncServerSyncPath[] = "/command/";
32 HttpResponse::HttpResponse()
33 : response_code(kUnsetResponseCode),
34 content_length(kUnsetContentLength),
35 payload_length(kUnsetPayloadLength),
36 server_status(NONE) {}
38 #define ENUM_CASE(x) case x: return #x; break
40 const char* HttpResponse::GetServerConnectionCodeString(
41 ServerConnectionCode code) {
44 ENUM_CASE(CONNECTION_UNAVAILABLE);
46 ENUM_CASE(SYNC_SERVER_ERROR);
47 ENUM_CASE(SYNC_AUTH_ERROR);
48 ENUM_CASE(SERVER_CONNECTION_OK);
57 // TODO(clamy): check if all errors are in the right category.
58 HttpResponse::ServerConnectionCode
59 HttpResponse::ServerConnectionCodeFromNetError(int error_code) {
61 case net::ERR_ABORTED:
62 case net::ERR_SOCKET_NOT_CONNECTED:
63 case net::ERR_NETWORK_CHANGED:
64 case net::ERR_CONNECTION_FAILED:
65 case net::ERR_NAME_NOT_RESOLVED:
66 case net::ERR_INTERNET_DISCONNECTED:
67 case net::ERR_NETWORK_ACCESS_DENIED:
68 case net::ERR_NETWORK_IO_SUSPENDED:
69 return CONNECTION_UNAVAILABLE;
74 ServerConnectionManager::Connection::Connection(
75 ServerConnectionManager* scm) : scm_(scm) {
78 ServerConnectionManager::Connection::~Connection() {
81 bool ServerConnectionManager::Connection::ReadBufferResponse(
83 HttpResponse* response,
84 bool require_response) {
85 if (net::HTTP_OK != response->response_code) {
86 response->server_status = HttpResponse::SYNC_SERVER_ERROR;
90 if (require_response && (1 > response->content_length))
93 const int64 bytes_read = ReadResponse(buffer_out,
94 static_cast<int>(response->content_length));
95 if (bytes_read != response->content_length) {
96 response->server_status = HttpResponse::IO_ERROR;
102 bool ServerConnectionManager::Connection::ReadDownloadResponse(
103 HttpResponse* response,
104 string* buffer_out) {
105 const int64 bytes_read = ReadResponse(buffer_out,
106 static_cast<int>(response->content_length));
108 if (bytes_read != response->content_length) {
109 LOG(ERROR) << "Mismatched content lengths, server claimed " <<
110 response->content_length << ", but sent " << bytes_read;
111 response->server_status = HttpResponse::IO_ERROR;
117 ServerConnectionManager::ScopedConnectionHelper::ScopedConnectionHelper(
118 ServerConnectionManager* manager, Connection* connection)
119 : manager_(manager), connection_(connection) {}
121 ServerConnectionManager::ScopedConnectionHelper::~ScopedConnectionHelper() {
123 manager_->OnConnectionDestroyed(connection_.get());
127 ServerConnectionManager::Connection*
128 ServerConnectionManager::ScopedConnectionHelper::get() {
129 return connection_.get();
134 string StripTrailingSlash(const string& s) {
135 int stripped_end_pos = s.size();
136 if (s.at(stripped_end_pos - 1) == '/') {
137 stripped_end_pos = stripped_end_pos - 1;
140 return s.substr(0, stripped_end_pos);
145 // TODO(chron): Use a GURL instead of string concatenation.
146 string ServerConnectionManager::Connection::MakeConnectionURL(
147 const string& sync_server,
149 bool use_ssl) const {
150 string connection_url = (use_ssl ? "https://" : "http://");
151 connection_url += sync_server;
152 connection_url = StripTrailingSlash(connection_url);
153 connection_url += path;
155 return connection_url;
158 int ServerConnectionManager::Connection::ReadResponse(string* out_buffer,
160 int bytes_read = buffer_.length();
161 CHECK(length <= bytes_read);
162 out_buffer->assign(buffer_);
166 ScopedServerStatusWatcher::ScopedServerStatusWatcher(
167 ServerConnectionManager* conn_mgr, HttpResponse* response)
168 : conn_mgr_(conn_mgr),
169 response_(response) {
170 response->server_status = conn_mgr->server_status_;
173 ScopedServerStatusWatcher::~ScopedServerStatusWatcher() {
174 conn_mgr_->SetServerStatus(response_->server_status);
177 ServerConnectionManager::ServerConnectionManager(
178 const string& server,
181 CancelationSignal* cancelation_signal)
182 : sync_server_(server),
183 sync_server_port_(port),
185 proto_sync_path_(kSyncServerSyncPath),
186 server_status_(HttpResponse::NONE),
188 active_connection_(NULL),
189 cancelation_signal_(cancelation_signal),
190 signal_handler_registered_(false) {
191 signal_handler_registered_ = cancelation_signal_->TryRegisterHandler(this);
192 if (!signal_handler_registered_) {
193 // Calling a virtual function from a constructor. We can get away with it
194 // here because ServerConnectionManager::OnSignalReceived() is the function
200 ServerConnectionManager::~ServerConnectionManager() {
201 if (signal_handler_registered_) {
202 cancelation_signal_->UnregisterHandler(this);
206 ServerConnectionManager::Connection*
207 ServerConnectionManager::MakeActiveConnection() {
208 base::AutoLock lock(terminate_connection_lock_);
209 DCHECK(!active_connection_);
213 active_connection_ = MakeConnection();
214 return active_connection_;
217 void ServerConnectionManager::OnConnectionDestroyed(Connection* connection) {
219 base::AutoLock lock(terminate_connection_lock_);
220 // |active_connection_| can be NULL already if it was aborted. Also,
221 // it can legitimately be a different Connection object if a new Connection
222 // was created after a previous one was Aborted and destroyed.
223 if (active_connection_ != connection)
226 active_connection_ = NULL;
229 bool ServerConnectionManager::SetAuthToken(const std::string& auth_token) {
230 DCHECK(thread_checker_.CalledOnValidThread());
231 if (previously_invalidated_token != auth_token) {
232 auth_token_.assign(auth_token);
233 previously_invalidated_token = std::string();
239 void ServerConnectionManager::OnInvalidationCredentialsRejected() {
240 InvalidateAndClearAuthToken();
241 SetServerStatus(HttpResponse::SYNC_AUTH_ERROR);
244 void ServerConnectionManager::InvalidateAndClearAuthToken() {
245 DCHECK(thread_checker_.CalledOnValidThread());
246 // Copy over the token to previous invalid token.
247 if (!auth_token_.empty()) {
248 previously_invalidated_token.assign(auth_token_);
249 auth_token_ = std::string();
253 void ServerConnectionManager::SetServerStatus(
254 HttpResponse::ServerConnectionCode server_status) {
255 if (server_status_ == server_status)
257 server_status_ = server_status;
258 NotifyStatusChanged();
261 void ServerConnectionManager::NotifyStatusChanged() {
262 DCHECK(thread_checker_.CalledOnValidThread());
263 FOR_EACH_OBSERVER(ServerConnectionEventListener, listeners_,
264 OnServerConnectionEvent(
265 ServerConnectionEvent(server_status_)));
268 bool ServerConnectionManager::PostBufferWithCachedAuth(
269 PostBufferParams* params, ScopedServerStatusWatcher* watcher) {
270 DCHECK(thread_checker_.CalledOnValidThread());
272 MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_));
273 return PostBufferToPath(params, path, auth_token(), watcher);
276 bool ServerConnectionManager::PostBufferToPath(PostBufferParams* params,
277 const string& path, const string& auth_token,
278 ScopedServerStatusWatcher* watcher) {
279 DCHECK(thread_checker_.CalledOnValidThread());
280 DCHECK(watcher != NULL);
282 // TODO(pavely): crbug.com/273096. Check for "credentials_lost" is added as
283 // workaround for M29 blocker to avoid sending RPC to sync with known invalid
284 // token but instead to trigger refreshing token in ProfileSyncService. Need
286 if (auth_token.empty() || auth_token == "credentials_lost") {
287 params->response.server_status = HttpResponse::SYNC_AUTH_ERROR;
291 // When our connection object falls out of scope, it clears itself from
292 // active_connection_.
293 ScopedConnectionHelper post(this, MakeActiveConnection());
295 params->response.server_status = HttpResponse::CONNECTION_UNAVAILABLE;
299 // Note that |post| may be aborted by now, which will just cause Init to fail
300 // with CONNECTION_UNAVAILABLE.
301 bool ok = post.get()->Init(
302 path.c_str(), auth_token, params->buffer_in, ¶ms->response);
304 if (params->response.server_status == HttpResponse::SYNC_AUTH_ERROR) {
305 InvalidateAndClearAuthToken();
308 if (!ok || net::HTTP_OK != params->response.response_code)
311 if (post.get()->ReadBufferResponse(
312 ¶ms->buffer_out, ¶ms->response, true)) {
313 params->response.server_status = HttpResponse::SERVER_CONNECTION_OK;
319 // Returns the current server parameters in server_url and port.
320 void ServerConnectionManager::GetServerParameters(string* server_url,
322 bool* use_ssl) const {
323 if (server_url != NULL)
324 *server_url = sync_server_;
326 *port = sync_server_port_;
331 std::string ServerConnectionManager::GetServerHost() const {
335 GetServerParameters(&server_url, &port, &use_ssl);
337 if (server_url.empty())
338 return std::string();
339 // We just want the hostname, so we don't need to switch on use_ssl.
340 server_url = "http://" + server_url;
341 GURL gurl(server_url);
342 DCHECK(gurl.is_valid()) << gurl;
346 void ServerConnectionManager::AddListener(
347 ServerConnectionEventListener* listener) {
348 DCHECK(thread_checker_.CalledOnValidThread());
349 listeners_.AddObserver(listener);
352 void ServerConnectionManager::RemoveListener(
353 ServerConnectionEventListener* listener) {
354 DCHECK(thread_checker_.CalledOnValidThread());
355 listeners_.RemoveObserver(listener);
358 ServerConnectionManager::Connection* ServerConnectionManager::MakeConnection()
360 return NULL; // For testing.
363 void ServerConnectionManager::OnSignalReceived() {
364 base::AutoLock lock(terminate_connection_lock_);
366 if (active_connection_)
367 active_connection_->Abort();
369 // Sever our ties to this connection object. Note that it still may exist,
370 // since we don't own it, but it has been neutered.
371 active_connection_ = NULL;
374 std::ostream& operator << (std::ostream& s, const struct HttpResponse& hr) {
375 s << " Response Code (bogus on error): " << hr.response_code;
376 s << " Content-Length (bogus on error): " << hr.content_length;
377 s << " Server Status: "
378 << HttpResponse::GetServerConnectionCodeString(hr.server_status);
382 } // namespace syncer