Enabling tests which should be fixed by r173829.
[chromium-blink-merge.git] / net / spdy / spdy_session_pool.cc
blobb967a463caa3fde9a1e28c7e84f1fbafe7a23f82
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 "net/spdy/spdy_session_pool.h"
7 #include "base/callback.h"
8 #include "base/logging.h"
9 #include "base/metrics/histogram.h"
10 #include "base/values.h"
11 #include "net/base/address_list.h"
12 #include "net/http/http_network_session.h"
13 #include "net/http/http_server_properties.h"
14 #include "net/spdy/spdy_session.h"
17 namespace net {
19 namespace {
21 enum SpdySessionGetTypes {
22 CREATED_NEW = 0,
23 FOUND_EXISTING = 1,
24 FOUND_EXISTING_FROM_IP_POOL = 2,
25 IMPORTED_FROM_SOCKET = 3,
26 SPDY_SESSION_GET_MAX = 4
29 bool HostPortProxyPairsAreEqual(const HostPortProxyPair& a,
30 const HostPortProxyPair& b) {
31 return a.first.Equals(b.first) && a.second == b.second;
36 // The maximum number of sessions to open to a single domain.
37 static const size_t kMaxSessionsPerDomain = 1;
39 SpdySessionPool::SpdySessionPool(
40 HostResolver* resolver,
41 SSLConfigService* ssl_config_service,
42 HttpServerProperties* http_server_properties,
43 size_t max_sessions_per_domain,
44 bool force_single_domain,
45 bool enable_ip_pooling,
46 bool enable_credential_frames,
47 bool enable_compression,
48 bool enable_ping_based_connection_checking,
49 NextProto default_protocol,
50 size_t initial_recv_window_size,
51 size_t initial_max_concurrent_streams,
52 size_t max_concurrent_streams_limit,
53 SpdySessionPool::TimeFunc time_func,
54 const std::string& trusted_spdy_proxy)
55 : http_server_properties_(http_server_properties),
56 ssl_config_service_(ssl_config_service),
57 resolver_(resolver),
58 verify_domain_authentication_(true),
59 enable_sending_initial_settings_(true),
60 max_sessions_per_domain_(max_sessions_per_domain == 0 ?
61 kMaxSessionsPerDomain :
62 max_sessions_per_domain),
63 force_single_domain_(force_single_domain),
64 enable_ip_pooling_(enable_ip_pooling),
65 enable_credential_frames_(enable_credential_frames),
66 enable_compression_(enable_compression),
67 enable_ping_based_connection_checking_(
68 enable_ping_based_connection_checking),
69 default_protocol_(default_protocol),
70 initial_recv_window_size_(initial_recv_window_size),
71 initial_max_concurrent_streams_(initial_max_concurrent_streams),
72 max_concurrent_streams_limit_(max_concurrent_streams_limit),
73 time_func_(time_func),
74 trusted_spdy_proxy_(
75 HostPortPair::FromString(trusted_spdy_proxy)) {
76 NetworkChangeNotifier::AddIPAddressObserver(this);
77 if (ssl_config_service_)
78 ssl_config_service_->AddObserver(this);
79 CertDatabase::GetInstance()->AddObserver(this);
82 SpdySessionPool::~SpdySessionPool() {
83 CloseAllSessions();
85 if (ssl_config_service_)
86 ssl_config_service_->RemoveObserver(this);
87 NetworkChangeNotifier::RemoveIPAddressObserver(this);
88 CertDatabase::GetInstance()->RemoveObserver(this);
91 scoped_refptr<SpdySession> SpdySessionPool::Get(
92 const HostPortProxyPair& host_port_proxy_pair,
93 const BoundNetLog& net_log) {
94 return GetInternal(host_port_proxy_pair, net_log, false);
97 scoped_refptr<SpdySession> SpdySessionPool::GetIfExists(
98 const HostPortProxyPair& host_port_proxy_pair,
99 const BoundNetLog& net_log) {
100 return GetInternal(host_port_proxy_pair, net_log, true);
103 scoped_refptr<SpdySession> SpdySessionPool::GetInternal(
104 const HostPortProxyPair& host_port_proxy_pair,
105 const BoundNetLog& net_log,
106 bool only_use_existing_sessions) {
107 scoped_refptr<SpdySession> spdy_session;
108 SpdySessionList* list = GetSessionList(host_port_proxy_pair);
109 if (!list) {
110 // Check if we have a Session through a domain alias.
111 spdy_session = GetFromAlias(host_port_proxy_pair, net_log, true);
112 if (spdy_session) {
113 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
114 FOUND_EXISTING_FROM_IP_POOL,
115 SPDY_SESSION_GET_MAX);
116 net_log.AddEvent(
117 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL,
118 spdy_session->net_log().source().ToEventParametersCallback());
119 // Add this session to the map so that we can find it next time.
120 list = AddSessionList(host_port_proxy_pair);
121 list->push_back(spdy_session);
122 spdy_session->AddPooledAlias(host_port_proxy_pair);
123 return spdy_session;
124 } else if (only_use_existing_sessions) {
125 return NULL;
127 list = AddSessionList(host_port_proxy_pair);
130 DCHECK(list);
131 if (list->size() && list->size() == max_sessions_per_domain_) {
132 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
133 FOUND_EXISTING,
134 SPDY_SESSION_GET_MAX);
135 spdy_session = GetExistingSession(list, net_log);
136 net_log.AddEvent(
137 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION,
138 spdy_session->net_log().source().ToEventParametersCallback());
139 return spdy_session;
142 DCHECK(!only_use_existing_sessions);
144 spdy_session = new SpdySession(host_port_proxy_pair, this,
145 http_server_properties_,
146 verify_domain_authentication_,
147 enable_sending_initial_settings_,
148 enable_credential_frames_,
149 enable_compression_,
150 enable_ping_based_connection_checking_,
151 default_protocol_,
152 initial_recv_window_size_,
153 initial_max_concurrent_streams_,
154 max_concurrent_streams_limit_,
155 time_func_,
156 trusted_spdy_proxy_,
157 net_log.net_log());
158 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
159 CREATED_NEW,
160 SPDY_SESSION_GET_MAX);
161 list->push_back(spdy_session);
162 net_log.AddEvent(
163 NetLog::TYPE_SPDY_SESSION_POOL_CREATED_NEW_SESSION,
164 spdy_session->net_log().source().ToEventParametersCallback());
165 DCHECK_LE(list->size(), max_sessions_per_domain_);
166 return spdy_session;
169 net::Error SpdySessionPool::GetSpdySessionFromSocket(
170 const HostPortProxyPair& host_port_proxy_pair,
171 ClientSocketHandle* connection,
172 const BoundNetLog& net_log,
173 int certificate_error_code,
174 scoped_refptr<SpdySession>* spdy_session,
175 bool is_secure) {
176 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
177 IMPORTED_FROM_SOCKET,
178 SPDY_SESSION_GET_MAX);
179 // Create the SPDY session and add it to the pool.
180 *spdy_session = new SpdySession(host_port_proxy_pair, this,
181 http_server_properties_,
182 verify_domain_authentication_,
183 enable_sending_initial_settings_,
184 enable_credential_frames_,
185 enable_compression_,
186 enable_ping_based_connection_checking_,
187 default_protocol_,
188 initial_recv_window_size_,
189 initial_max_concurrent_streams_,
190 max_concurrent_streams_limit_,
191 time_func_,
192 trusted_spdy_proxy_,
193 net_log.net_log());
194 SpdySessionList* list = GetSessionList(host_port_proxy_pair);
195 if (!list)
196 list = AddSessionList(host_port_proxy_pair);
197 DCHECK(list->empty());
198 list->push_back(*spdy_session);
200 net_log.AddEvent(
201 NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
202 (*spdy_session)->net_log().source().ToEventParametersCallback());
204 // We have a new session. Lookup the IP address for this session so that we
205 // can match future Sessions (potentially to different domains) which can
206 // potentially be pooled with this one. Because GetPeerAddress() reports the
207 // proxy's address instead of the origin server, check to see if this is a
208 // direct connection.
209 if (enable_ip_pooling_ && host_port_proxy_pair.second.is_direct()) {
210 IPEndPoint address;
211 if (connection->socket()->GetPeerAddress(&address) == OK)
212 AddAlias(address, host_port_proxy_pair);
215 // Now we can initialize the session with the SSL socket.
216 return (*spdy_session)->InitializeWithSocket(connection, is_secure,
217 certificate_error_code);
220 bool SpdySessionPool::HasSession(
221 const HostPortProxyPair& host_port_proxy_pair) const {
222 if (GetSessionList(host_port_proxy_pair))
223 return true;
225 // Check if we have a session via an alias.
226 scoped_refptr<SpdySession> spdy_session =
227 GetFromAlias(host_port_proxy_pair, BoundNetLog(), false);
228 return spdy_session.get() != NULL;
231 void SpdySessionPool::Remove(const scoped_refptr<SpdySession>& session) {
232 bool ok = RemoveFromSessionList(session, session->host_port_proxy_pair());
233 DCHECK(ok);
234 session->net_log().AddEvent(
235 NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION,
236 session->net_log().source().ToEventParametersCallback());
238 const std::set<HostPortProxyPair>& aliases = session->pooled_aliases();
239 for (std::set<HostPortProxyPair>::const_iterator it = aliases.begin();
240 it != aliases.end(); ++it) {
241 ok = RemoveFromSessionList(session, *it);
242 DCHECK(ok);
246 bool SpdySessionPool::RemoveFromSessionList(
247 const scoped_refptr<SpdySession>& session,
248 const HostPortProxyPair& pair) {
249 SpdySessionList* list = GetSessionList(pair);
250 if (!list)
251 return false;
252 list->remove(session);
253 if (list->empty())
254 RemoveSessionList(pair);
255 return true;
258 Value* SpdySessionPool::SpdySessionPoolInfoToValue() const {
259 ListValue* list = new ListValue();
261 for (SpdySessionsMap::const_iterator it = sessions_.begin();
262 it != sessions_.end(); ++it) {
263 SpdySessionList* sessions = it->second;
264 for (SpdySessionList::const_iterator session = sessions->begin();
265 session != sessions->end(); ++session) {
266 // Only add the session if the key in the map matches the main
267 // host_port_proxy_pair (not an alias).
268 const HostPortProxyPair& key = it->first;
269 const HostPortProxyPair& pair = session->get()->host_port_proxy_pair();
270 if (key.first.Equals(pair.first) && key.second == pair.second)
271 list->Append(session->get()->GetInfoAsValue());
274 return list;
277 void SpdySessionPool::OnIPAddressChanged() {
278 CloseCurrentSessions(ERR_NETWORK_CHANGED);
279 http_server_properties_->ClearSpdySettings();
282 void SpdySessionPool::OnSSLConfigChanged() {
283 CloseCurrentSessions(ERR_NETWORK_CHANGED);
286 scoped_refptr<SpdySession> SpdySessionPool::GetExistingSession(
287 SpdySessionList* list,
288 const BoundNetLog& net_log) const {
289 DCHECK(list);
290 DCHECK_LT(0u, list->size());
291 scoped_refptr<SpdySession> spdy_session = list->front();
292 if (list->size() > 1) {
293 list->pop_front(); // Rotate the list.
294 list->push_back(spdy_session);
297 return spdy_session;
300 scoped_refptr<SpdySession> SpdySessionPool::GetFromAlias(
301 const HostPortProxyPair& host_port_proxy_pair,
302 const BoundNetLog& net_log,
303 bool record_histograms) const {
304 // We should only be checking aliases when there is no direct session.
305 DCHECK(!GetSessionList(host_port_proxy_pair));
307 if (!enable_ip_pooling_)
308 return NULL;
310 AddressList addresses;
311 if (!LookupAddresses(host_port_proxy_pair, net_log, &addresses))
312 return NULL;
313 for (AddressList::const_iterator iter = addresses.begin();
314 iter != addresses.end();
315 ++iter) {
316 SpdyAliasMap::const_iterator alias_iter = aliases_.find(*iter);
317 if (alias_iter == aliases_.end())
318 continue;
320 // We found an alias.
321 const HostPortProxyPair& alias_pair = alias_iter->second;
323 // If the proxy settings match, we can reuse this session.
324 if (!(alias_pair.second == host_port_proxy_pair.second))
325 continue;
327 SpdySessionList* list = GetSessionList(alias_pair);
328 if (!list) {
329 NOTREACHED(); // It shouldn't be in the aliases table if we can't get it!
330 continue;
333 scoped_refptr<SpdySession> spdy_session = GetExistingSession(list, net_log);
334 // If the SPDY session is a secure one, we need to verify that the server
335 // is authenticated to serve traffic for |host_port_proxy_pair| too.
336 if (!spdy_session->VerifyDomainAuthentication(
337 host_port_proxy_pair.first.host())) {
338 if (record_histograms)
339 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2);
340 continue;
342 if (record_histograms)
343 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2);
344 return spdy_session;
346 return NULL;
349 void SpdySessionPool::OnCertAdded(const X509Certificate* cert) {
350 CloseCurrentSessions(ERR_NETWORK_CHANGED);
353 void SpdySessionPool::OnCertTrustChanged(const X509Certificate* cert) {
354 // Per wtc, we actually only need to CloseCurrentSessions when trust is
355 // reduced. CloseCurrentSessions now because OnCertTrustChanged does not
356 // tell us this.
357 // See comments in ClientSocketPoolManager::OnCertTrustChanged.
358 CloseCurrentSessions(ERR_NETWORK_CHANGED);
361 const HostPortProxyPair& SpdySessionPool::NormalizeListPair(
362 const HostPortProxyPair& host_port_proxy_pair) const {
363 if (!force_single_domain_)
364 return host_port_proxy_pair;
366 static HostPortProxyPair* single_domain_pair = NULL;
367 if (!single_domain_pair) {
368 HostPortPair single_domain = HostPortPair("singledomain.com", 80);
369 single_domain_pair = new HostPortProxyPair(single_domain,
370 ProxyServer::Direct());
372 return *single_domain_pair;
375 SpdySessionPool::SpdySessionList*
376 SpdySessionPool::AddSessionList(
377 const HostPortProxyPair& host_port_proxy_pair) {
378 const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
379 DCHECK(sessions_.find(pair) == sessions_.end());
380 SpdySessionPool::SpdySessionList* list = new SpdySessionList();
381 sessions_[pair] = list;
382 return list;
385 SpdySessionPool::SpdySessionList*
386 SpdySessionPool::GetSessionList(
387 const HostPortProxyPair& host_port_proxy_pair) const {
388 const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
389 SpdySessionsMap::const_iterator it = sessions_.find(pair);
390 if (it != sessions_.end())
391 return it->second;
392 return NULL;
395 void SpdySessionPool::RemoveSessionList(
396 const HostPortProxyPair& host_port_proxy_pair) {
397 const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
398 SpdySessionList* list = GetSessionList(pair);
399 if (list) {
400 delete list;
401 sessions_.erase(pair);
402 } else {
403 DCHECK(false) << "removing orphaned session list";
405 RemoveAliases(host_port_proxy_pair);
408 bool SpdySessionPool::LookupAddresses(const HostPortProxyPair& pair,
409 const BoundNetLog& net_log,
410 AddressList* addresses) const {
411 net::HostResolver::RequestInfo resolve_info(pair.first);
412 int rv = resolver_->ResolveFromCache(resolve_info, addresses, net_log);
413 DCHECK_NE(ERR_IO_PENDING, rv);
414 return rv == OK;
417 void SpdySessionPool::AddAlias(const IPEndPoint& endpoint,
418 const HostPortProxyPair& pair) {
419 DCHECK(enable_ip_pooling_);
420 aliases_[endpoint] = pair;
423 void SpdySessionPool::RemoveAliases(const HostPortProxyPair& pair) {
424 // Walk the aliases map, find references to this pair.
425 // TODO(mbelshe): Figure out if this is too expensive.
426 SpdyAliasMap::iterator alias_it = aliases_.begin();
427 while (alias_it != aliases_.end()) {
428 if (HostPortProxyPairsAreEqual(alias_it->second, pair)) {
429 aliases_.erase(alias_it);
430 alias_it = aliases_.begin(); // Iterator was invalidated.
431 continue;
433 ++alias_it;
437 void SpdySessionPool::CloseAllSessions() {
438 while (!sessions_.empty()) {
439 SpdySessionList* list = sessions_.begin()->second;
440 CHECK(list);
441 const scoped_refptr<SpdySession>& session = list->front();
442 CHECK(session);
443 // This call takes care of removing the session from the pool, as well as
444 // removing the session list if the list is empty.
445 session->CloseSessionOnError(
446 net::ERR_ABORTED, true, "Closing all sessions.");
450 void SpdySessionPool::CloseCurrentSessions(net::Error error) {
451 SpdySessionsMap old_map;
452 old_map.swap(sessions_);
453 for (SpdySessionsMap::const_iterator it = old_map.begin();
454 it != old_map.end(); ++it) {
455 SpdySessionList* list = it->second;
456 CHECK(list);
457 const scoped_refptr<SpdySession>& session = list->front();
458 CHECK(session);
459 session->set_spdy_session_pool(NULL);
462 while (!old_map.empty()) {
463 SpdySessionList* list = old_map.begin()->second;
464 CHECK(list);
465 const scoped_refptr<SpdySession>& session = list->front();
466 CHECK(session);
467 session->CloseSessionOnError(error, false, "Closing current sessions.");
468 list->pop_front();
469 if (list->empty()) {
470 delete list;
471 RemoveAliases(old_map.begin()->first);
472 old_map.erase(old_map.begin()->first);
475 DCHECK(sessions_.empty());
476 DCHECK(aliases_.empty());
479 void SpdySessionPool::CloseIdleSessions() {
480 SpdySessionsMap::const_iterator map_it = sessions_.begin();
481 while (map_it != sessions_.end()) {
482 SpdySessionList* list = map_it->second;
483 ++map_it;
484 CHECK(list);
486 // Assumes there is only 1 element in the list
487 SpdySessionList::iterator session_it = list->begin();
488 const scoped_refptr<SpdySession>& session = *session_it;
489 CHECK(session);
490 if (!session->is_active()) {
491 session->CloseSessionOnError(
492 net::ERR_ABORTED, true, "Closing idle sessions.");
497 } // namespace net