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();
237 // This could happen in case like server outage/bug. E.g. token returned by
238 // first request is considered invalid by sync server and because
239 // of token server's caching policy, etc, same token is returned on second
240 // request. Need to notify sync frontend again to request new token,
241 // otherwise backend will stay in SYNC_AUTH_ERROR state while frontend thinks
242 // everything is fine and takes no actions.
243 SetServerStatus(HttpResponse::SYNC_AUTH_ERROR
);
247 void ServerConnectionManager::InvalidateAndClearAuthToken() {
248 DCHECK(thread_checker_
.CalledOnValidThread());
249 // Copy over the token to previous invalid token.
250 if (!auth_token_
.empty()) {
251 previously_invalidated_token
.assign(auth_token_
);
252 auth_token_
= std::string();
256 void ServerConnectionManager::SetServerStatus(
257 HttpResponse::ServerConnectionCode server_status
) {
258 // SYNC_AUTH_ERROR is permanent error. Need to notify observer to take
259 // action externally to resolve.
260 if (server_status
!= HttpResponse::SYNC_AUTH_ERROR
&&
261 server_status_
== server_status
) {
264 server_status_
= server_status
;
265 NotifyStatusChanged();
268 void ServerConnectionManager::NotifyStatusChanged() {
269 DCHECK(thread_checker_
.CalledOnValidThread());
270 FOR_EACH_OBSERVER(ServerConnectionEventListener
, listeners_
,
271 OnServerConnectionEvent(
272 ServerConnectionEvent(server_status_
)));
275 bool ServerConnectionManager::PostBufferWithCachedAuth(
276 PostBufferParams
* params
, ScopedServerStatusWatcher
* watcher
) {
277 DCHECK(thread_checker_
.CalledOnValidThread());
279 MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_
));
280 return PostBufferToPath(params
, path
, auth_token(), watcher
);
283 bool ServerConnectionManager::PostBufferToPath(PostBufferParams
* params
,
284 const string
& path
, const string
& auth_token
,
285 ScopedServerStatusWatcher
* watcher
) {
286 DCHECK(thread_checker_
.CalledOnValidThread());
287 DCHECK(watcher
!= NULL
);
289 // TODO(pavely): crbug.com/273096. Check for "credentials_lost" is added as
290 // workaround for M29 blocker to avoid sending RPC to sync with known invalid
291 // token but instead to trigger refreshing token in ProfileSyncService. Need
293 if (auth_token
.empty() || auth_token
== "credentials_lost") {
294 params
->response
.server_status
= HttpResponse::SYNC_AUTH_ERROR
;
295 // Print a log to distinguish this "known failure" from others.
296 LOG(WARNING
) << "ServerConnectionManager forcing SYNC_AUTH_ERROR";
300 // When our connection object falls out of scope, it clears itself from
301 // active_connection_.
302 ScopedConnectionHelper
post(this, MakeActiveConnection());
304 params
->response
.server_status
= HttpResponse::CONNECTION_UNAVAILABLE
;
308 // Note that |post| may be aborted by now, which will just cause Init to fail
309 // with CONNECTION_UNAVAILABLE.
310 bool ok
= post
.get()->Init(
311 path
.c_str(), auth_token
, params
->buffer_in
, ¶ms
->response
);
313 if (params
->response
.server_status
== HttpResponse::SYNC_AUTH_ERROR
) {
314 InvalidateAndClearAuthToken();
317 if (!ok
|| net::HTTP_OK
!= params
->response
.response_code
)
320 if (post
.get()->ReadBufferResponse(
321 ¶ms
->buffer_out
, ¶ms
->response
, true)) {
322 params
->response
.server_status
= HttpResponse::SERVER_CONNECTION_OK
;
328 // Returns the current server parameters in server_url and port.
329 void ServerConnectionManager::GetServerParameters(string
* server_url
,
331 bool* use_ssl
) const {
332 if (server_url
!= NULL
)
333 *server_url
= sync_server_
;
335 *port
= sync_server_port_
;
340 std::string
ServerConnectionManager::GetServerHost() const {
344 GetServerParameters(&server_url
, &port
, &use_ssl
);
346 if (server_url
.empty())
347 return std::string();
348 // We just want the hostname, so we don't need to switch on use_ssl.
349 server_url
= "http://" + server_url
;
350 GURL
gurl(server_url
);
351 DCHECK(gurl
.is_valid()) << gurl
;
355 void ServerConnectionManager::AddListener(
356 ServerConnectionEventListener
* listener
) {
357 DCHECK(thread_checker_
.CalledOnValidThread());
358 listeners_
.AddObserver(listener
);
361 void ServerConnectionManager::RemoveListener(
362 ServerConnectionEventListener
* listener
) {
363 DCHECK(thread_checker_
.CalledOnValidThread());
364 listeners_
.RemoveObserver(listener
);
367 ServerConnectionManager::Connection
* ServerConnectionManager::MakeConnection()
369 return NULL
; // For testing.
372 void ServerConnectionManager::OnSignalReceived() {
373 base::AutoLock
lock(terminate_connection_lock_
);
375 if (active_connection_
)
376 active_connection_
->Abort();
378 // Sever our ties to this connection object. Note that it still may exist,
379 // since we don't own it, but it has been neutered.
380 active_connection_
= NULL
;
383 std::ostream
& operator << (std::ostream
& s
, const struct HttpResponse
& hr
) {
384 s
<< " Response Code (bogus on error): " << hr
.response_code
;
385 s
<< " Content-Length (bogus on error): " << hr
.content_length
;
386 s
<< " Server Status: "
387 << HttpResponse::GetServerConnectionCodeString(hr
.server_status
);
391 } // namespace syncer