OLD | NEW |
| (Empty) |
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 #include <stdint.h> | |
9 | |
10 #include <ostream> | |
11 #include <string> | |
12 #include <vector> | |
13 | |
14 #include "base/metrics/histogram.h" | |
15 #include "build/build_config.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/internal_api/public/base/cancelation_signal.h" | |
21 #include "sync/protocol/sync.pb.h" | |
22 #include "sync/syncable/directory.h" | |
23 #include "url/gurl.h" | |
24 | |
25 namespace syncer { | |
26 | |
27 using std::ostream; | |
28 using std::string; | |
29 using std::vector; | |
30 | |
31 static const char kSyncServerSyncPath[] = "/command/"; | |
32 | |
33 HttpResponse::HttpResponse() | |
34 : response_code(kUnsetResponseCode), | |
35 content_length(kUnsetContentLength), | |
36 payload_length(kUnsetPayloadLength), | |
37 server_status(NONE) {} | |
38 | |
39 #define ENUM_CASE(x) case x: return #x; break | |
40 | |
41 const char* HttpResponse::GetServerConnectionCodeString( | |
42 ServerConnectionCode code) { | |
43 switch (code) { | |
44 ENUM_CASE(NONE); | |
45 ENUM_CASE(CONNECTION_UNAVAILABLE); | |
46 ENUM_CASE(IO_ERROR); | |
47 ENUM_CASE(SYNC_SERVER_ERROR); | |
48 ENUM_CASE(SYNC_AUTH_ERROR); | |
49 ENUM_CASE(SERVER_CONNECTION_OK); | |
50 ENUM_CASE(RETRY); | |
51 } | |
52 NOTREACHED(); | |
53 return ""; | |
54 } | |
55 | |
56 #undef ENUM_CASE | |
57 | |
58 ServerConnectionManager::Connection::Connection( | |
59 ServerConnectionManager* scm) : scm_(scm) { | |
60 } | |
61 | |
62 ServerConnectionManager::Connection::~Connection() { | |
63 } | |
64 | |
65 bool ServerConnectionManager::Connection::ReadBufferResponse( | |
66 string* buffer_out, | |
67 HttpResponse* response, | |
68 bool require_response) { | |
69 if (net::HTTP_OK != response->response_code) { | |
70 response->server_status = HttpResponse::SYNC_SERVER_ERROR; | |
71 return false; | |
72 } | |
73 | |
74 if (require_response && (1 > response->content_length)) | |
75 return false; | |
76 | |
77 const int64_t bytes_read = | |
78 ReadResponse(buffer_out, static_cast<int>(response->content_length)); | |
79 if (bytes_read != response->content_length) { | |
80 response->server_status = HttpResponse::IO_ERROR; | |
81 return false; | |
82 } | |
83 return true; | |
84 } | |
85 | |
86 bool ServerConnectionManager::Connection::ReadDownloadResponse( | |
87 HttpResponse* response, | |
88 string* buffer_out) { | |
89 const int64_t bytes_read = | |
90 ReadResponse(buffer_out, static_cast<int>(response->content_length)); | |
91 | |
92 if (bytes_read != response->content_length) { | |
93 LOG(ERROR) << "Mismatched content lengths, server claimed " << | |
94 response->content_length << ", but sent " << bytes_read; | |
95 response->server_status = HttpResponse::IO_ERROR; | |
96 return false; | |
97 } | |
98 return true; | |
99 } | |
100 | |
101 ServerConnectionManager::ScopedConnectionHelper::ScopedConnectionHelper( | |
102 ServerConnectionManager* manager, Connection* connection) | |
103 : manager_(manager), connection_(connection) {} | |
104 | |
105 ServerConnectionManager::ScopedConnectionHelper::~ScopedConnectionHelper() { | |
106 if (connection_) | |
107 manager_->OnConnectionDestroyed(connection_.get()); | |
108 connection_.reset(); | |
109 } | |
110 | |
111 ServerConnectionManager::Connection* | |
112 ServerConnectionManager::ScopedConnectionHelper::get() { | |
113 return connection_.get(); | |
114 } | |
115 | |
116 namespace { | |
117 | |
118 string StripTrailingSlash(const string& s) { | |
119 int stripped_end_pos = s.size(); | |
120 if (s.at(stripped_end_pos - 1) == '/') { | |
121 stripped_end_pos = stripped_end_pos - 1; | |
122 } | |
123 | |
124 return s.substr(0, stripped_end_pos); | |
125 } | |
126 | |
127 } // namespace | |
128 | |
129 // TODO(chron): Use a GURL instead of string concatenation. | |
130 string ServerConnectionManager::Connection::MakeConnectionURL( | |
131 const string& sync_server, | |
132 const string& path, | |
133 bool use_ssl) const { | |
134 string connection_url = (use_ssl ? "https://" : "http://"); | |
135 connection_url += sync_server; | |
136 connection_url = StripTrailingSlash(connection_url); | |
137 connection_url += path; | |
138 | |
139 return connection_url; | |
140 } | |
141 | |
142 int ServerConnectionManager::Connection::ReadResponse(string* out_buffer, | |
143 int length) { | |
144 int bytes_read = buffer_.length(); | |
145 CHECK(length <= bytes_read); | |
146 out_buffer->assign(buffer_); | |
147 return bytes_read; | |
148 } | |
149 | |
150 ServerConnectionManager::ServerConnectionManager( | |
151 const string& server, | |
152 int port, | |
153 bool use_ssl, | |
154 CancelationSignal* cancelation_signal) | |
155 : sync_server_(server), | |
156 sync_server_port_(port), | |
157 use_ssl_(use_ssl), | |
158 proto_sync_path_(kSyncServerSyncPath), | |
159 server_status_(HttpResponse::NONE), | |
160 terminated_(false), | |
161 active_connection_(NULL), | |
162 cancelation_signal_(cancelation_signal), | |
163 signal_handler_registered_(false) { | |
164 signal_handler_registered_ = cancelation_signal_->TryRegisterHandler(this); | |
165 if (!signal_handler_registered_) { | |
166 // Calling a virtual function from a constructor. We can get away with it | |
167 // here because ServerConnectionManager::OnSignalReceived() is the function | |
168 // we want to call. | |
169 OnSignalReceived(); | |
170 } | |
171 } | |
172 | |
173 ServerConnectionManager::~ServerConnectionManager() { | |
174 if (signal_handler_registered_) { | |
175 cancelation_signal_->UnregisterHandler(this); | |
176 } | |
177 } | |
178 | |
179 ServerConnectionManager::Connection* | |
180 ServerConnectionManager::MakeActiveConnection() { | |
181 base::AutoLock lock(terminate_connection_lock_); | |
182 DCHECK(!active_connection_); | |
183 if (terminated_) | |
184 return NULL; | |
185 | |
186 active_connection_ = MakeConnection(); | |
187 return active_connection_; | |
188 } | |
189 | |
190 void ServerConnectionManager::OnConnectionDestroyed(Connection* connection) { | |
191 DCHECK(connection); | |
192 base::AutoLock lock(terminate_connection_lock_); | |
193 // |active_connection_| can be NULL already if it was aborted. Also, | |
194 // it can legitimately be a different Connection object if a new Connection | |
195 // was created after a previous one was Aborted and destroyed. | |
196 if (active_connection_ != connection) | |
197 return; | |
198 | |
199 active_connection_ = NULL; | |
200 } | |
201 | |
202 bool ServerConnectionManager::SetAuthToken(const std::string& auth_token) { | |
203 DCHECK(thread_checker_.CalledOnValidThread()); | |
204 if (previously_invalidated_token != auth_token) { | |
205 auth_token_.assign(auth_token); | |
206 previously_invalidated_token = std::string(); | |
207 return true; | |
208 } | |
209 | |
210 // This could happen in case like server outage/bug. E.g. token returned by | |
211 // first request is considered invalid by sync server and because | |
212 // of token server's caching policy, etc, same token is returned on second | |
213 // request. Need to notify sync frontend again to request new token, | |
214 // otherwise backend will stay in SYNC_AUTH_ERROR state while frontend thinks | |
215 // everything is fine and takes no actions. | |
216 SetServerStatus(HttpResponse::SYNC_AUTH_ERROR); | |
217 return false; | |
218 } | |
219 | |
220 void 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 | |
229 void ServerConnectionManager::SetServerStatus( | |
230 HttpResponse::ServerConnectionCode server_status) { | |
231 // SYNC_AUTH_ERROR is permanent error. Need to notify observer to take | |
232 // action externally to resolve. | |
233 if (server_status != HttpResponse::SYNC_AUTH_ERROR && | |
234 server_status_ == server_status) { | |
235 return; | |
236 } | |
237 server_status_ = server_status; | |
238 NotifyStatusChanged(); | |
239 } | |
240 | |
241 void ServerConnectionManager::NotifyStatusChanged() { | |
242 DCHECK(thread_checker_.CalledOnValidThread()); | |
243 FOR_EACH_OBSERVER(ServerConnectionEventListener, listeners_, | |
244 OnServerConnectionEvent( | |
245 ServerConnectionEvent(server_status_))); | |
246 } | |
247 | |
248 bool ServerConnectionManager::PostBufferWithCachedAuth( | |
249 PostBufferParams* params) { | |
250 DCHECK(thread_checker_.CalledOnValidThread()); | |
251 string path = | |
252 MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_)); | |
253 bool result = PostBufferToPath(params, path, auth_token()); | |
254 SetServerStatus(params->response.server_status); | |
255 return result; | |
256 } | |
257 | |
258 bool ServerConnectionManager::PostBufferToPath(PostBufferParams* params, | |
259 const string& path, | |
260 const string& auth_token) { | |
261 DCHECK(thread_checker_.CalledOnValidThread()); | |
262 | |
263 // TODO(pavely): crbug.com/273096. Check for "credentials_lost" is added as | |
264 // workaround for M29 blocker to avoid sending RPC to sync with known invalid | |
265 // token but instead to trigger refreshing token in ProfileSyncService. Need | |
266 // to clean it. | |
267 if (auth_token.empty() || auth_token == "credentials_lost") { | |
268 params->response.server_status = HttpResponse::SYNC_AUTH_ERROR; | |
269 // Print a log to distinguish this "known failure" from others. | |
270 LOG(WARNING) << "ServerConnectionManager forcing SYNC_AUTH_ERROR"; | |
271 return false; | |
272 } | |
273 | |
274 // When our connection object falls out of scope, it clears itself from | |
275 // active_connection_. | |
276 ScopedConnectionHelper post(this, MakeActiveConnection()); | |
277 if (!post.get()) { | |
278 params->response.server_status = HttpResponse::CONNECTION_UNAVAILABLE; | |
279 return false; | |
280 } | |
281 | |
282 // Note that |post| may be aborted by now, which will just cause Init to fail | |
283 // with CONNECTION_UNAVAILABLE. | |
284 bool ok = post.get()->Init( | |
285 path.c_str(), auth_token, params->buffer_in, ¶ms->response); | |
286 | |
287 if (params->response.server_status == HttpResponse::SYNC_AUTH_ERROR) { | |
288 InvalidateAndClearAuthToken(); | |
289 } | |
290 | |
291 if (!ok || net::HTTP_OK != params->response.response_code) | |
292 return false; | |
293 | |
294 if (post.get()->ReadBufferResponse( | |
295 ¶ms->buffer_out, ¶ms->response, true)) { | |
296 params->response.server_status = HttpResponse::SERVER_CONNECTION_OK; | |
297 return true; | |
298 } | |
299 return false; | |
300 } | |
301 | |
302 void ServerConnectionManager::AddListener( | |
303 ServerConnectionEventListener* listener) { | |
304 DCHECK(thread_checker_.CalledOnValidThread()); | |
305 listeners_.AddObserver(listener); | |
306 } | |
307 | |
308 void ServerConnectionManager::RemoveListener( | |
309 ServerConnectionEventListener* listener) { | |
310 DCHECK(thread_checker_.CalledOnValidThread()); | |
311 listeners_.RemoveObserver(listener); | |
312 } | |
313 | |
314 ServerConnectionManager::Connection* ServerConnectionManager::MakeConnection() { | |
315 return NULL; // For testing. | |
316 } | |
317 | |
318 void ServerConnectionManager::OnSignalReceived() { | |
319 base::AutoLock lock(terminate_connection_lock_); | |
320 terminated_ = true; | |
321 if (active_connection_) | |
322 active_connection_->Abort(); | |
323 | |
324 // Sever our ties to this connection object. Note that it still may exist, | |
325 // since we don't own it, but it has been neutered. | |
326 active_connection_ = NULL; | |
327 } | |
328 | |
329 std::ostream& operator << (std::ostream& s, const struct HttpResponse& hr) { | |
330 s << " Response Code (bogus on error): " << hr.response_code; | |
331 s << " Content-Length (bogus on error): " << hr.content_length; | |
332 s << " Server Status: " | |
333 << HttpResponse::GetServerConnectionCodeString(hr.server_status); | |
334 return s; | |
335 } | |
336 | |
337 } // namespace syncer | |
OLD | NEW |