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, &params->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      &params->buffer_out, &params->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