Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / sync / engine / net / server_connection_manager.cc
blob3101109a4fe86458a3008c3fe2f4c2db26592e9b
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"
7 #include <errno.h>
9 #include <ostream>
10 #include <string>
11 #include <vector>
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"
22 #include "url/gurl.h"
24 namespace syncer {
26 using std::ostream;
27 using std::string;
28 using std::vector;
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) {
42 switch (code) {
43 ENUM_CASE(NONE);
44 ENUM_CASE(CONNECTION_UNAVAILABLE);
45 ENUM_CASE(IO_ERROR);
46 ENUM_CASE(SYNC_SERVER_ERROR);
47 ENUM_CASE(SYNC_AUTH_ERROR);
48 ENUM_CASE(SERVER_CONNECTION_OK);
49 ENUM_CASE(RETRY);
51 NOTREACHED();
52 return "";
55 #undef ENUM_CASE
57 // TODO(clamy): check if all errors are in the right category.
58 HttpResponse::ServerConnectionCode
59 HttpResponse::ServerConnectionCodeFromNetError(int error_code) {
60 switch (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;
71 return IO_ERROR;
74 ServerConnectionManager::Connection::Connection(
75 ServerConnectionManager* scm) : scm_(scm) {
78 ServerConnectionManager::Connection::~Connection() {
81 bool ServerConnectionManager::Connection::ReadBufferResponse(
82 string* buffer_out,
83 HttpResponse* response,
84 bool require_response) {
85 if (net::HTTP_OK != response->response_code) {
86 response->server_status = HttpResponse::SYNC_SERVER_ERROR;
87 return false;
90 if (require_response && (1 > response->content_length))
91 return false;
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;
97 return false;
99 return true;
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;
112 return false;
114 return true;
117 ServerConnectionManager::ScopedConnectionHelper::ScopedConnectionHelper(
118 ServerConnectionManager* manager, Connection* connection)
119 : manager_(manager), connection_(connection) {}
121 ServerConnectionManager::ScopedConnectionHelper::~ScopedConnectionHelper() {
122 if (connection_)
123 manager_->OnConnectionDestroyed(connection_.get());
124 connection_.reset();
127 ServerConnectionManager::Connection*
128 ServerConnectionManager::ScopedConnectionHelper::get() {
129 return connection_.get();
132 namespace {
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);
143 } // namespace
145 // TODO(chron): Use a GURL instead of string concatenation.
146 string ServerConnectionManager::Connection::MakeConnectionURL(
147 const string& sync_server,
148 const string& path,
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,
159 int length) {
160 int bytes_read = buffer_.length();
161 CHECK(length <= bytes_read);
162 out_buffer->assign(buffer_);
163 return bytes_read;
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,
179 int port,
180 bool use_ssl,
181 CancelationSignal* cancelation_signal)
182 : sync_server_(server),
183 sync_server_port_(port),
184 use_ssl_(use_ssl),
185 proto_sync_path_(kSyncServerSyncPath),
186 server_status_(HttpResponse::NONE),
187 terminated_(false),
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
195 // we want to call.
196 OnSignalReceived();
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_);
210 if (terminated_)
211 return NULL;
213 active_connection_ = MakeConnection();
214 return active_connection_;
217 void ServerConnectionManager::OnConnectionDestroyed(Connection* connection) {
218 DCHECK(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)
224 return;
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();
234 return true;
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);
244 return false;
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) {
262 return;
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());
278 string path =
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
292 // to clean it.
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";
297 return false;
300 // When our connection object falls out of scope, it clears itself from
301 // active_connection_.
302 ScopedConnectionHelper post(this, MakeActiveConnection());
303 if (!post.get()) {
304 params->response.server_status = HttpResponse::CONNECTION_UNAVAILABLE;
305 return false;
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, &params->response);
313 if (params->response.server_status == HttpResponse::SYNC_AUTH_ERROR) {
314 InvalidateAndClearAuthToken();
317 if (!ok || net::HTTP_OK != params->response.response_code)
318 return false;
320 if (post.get()->ReadBufferResponse(
321 &params->buffer_out, &params->response, true)) {
322 params->response.server_status = HttpResponse::SERVER_CONNECTION_OK;
323 return true;
325 return false;
328 // Returns the current server parameters in server_url and port.
329 void ServerConnectionManager::GetServerParameters(string* server_url,
330 int* port,
331 bool* use_ssl) const {
332 if (server_url != NULL)
333 *server_url = sync_server_;
334 if (port != NULL)
335 *port = sync_server_port_;
336 if (use_ssl != NULL)
337 *use_ssl = use_ssl_;
340 std::string ServerConnectionManager::GetServerHost() const {
341 string server_url;
342 int port;
343 bool use_ssl;
344 GetServerParameters(&server_url, &port, &use_ssl);
345 // For unit tests.
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;
352 return gurl.host();
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_);
374 terminated_ = true;
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);
388 return s;
391 } // namespace syncer