// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/http/http_server_properties_impl.h" #include "base/bind.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/stl_util.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" namespace net { namespace { const uint64 kBrokenAlternateProtocolDelaySecs = 300; } // namespace HttpServerPropertiesImpl::HttpServerPropertiesImpl() : spdy_servers_map_(SpdyServerHostPortMap::NO_AUTO_EVICT), alternate_protocol_map_(AlternateProtocolMap::NO_AUTO_EVICT), alternate_protocol_experiment_( ALTERNATE_PROTOCOL_NOT_PART_OF_EXPERIMENT), spdy_settings_map_(SpdySettingsMap::NO_AUTO_EVICT), weak_ptr_factory_(this) { canoncial_suffixes_.push_back(".c.youtube.com"); canoncial_suffixes_.push_back(".googlevideo.com"); canoncial_suffixes_.push_back(".googleusercontent.com"); } HttpServerPropertiesImpl::~HttpServerPropertiesImpl() { } void HttpServerPropertiesImpl::InitializeSpdyServers( std::vector<std::string>* spdy_servers, bool support_spdy) { DCHECK(CalledOnValidThread()); if (!spdy_servers) return; // Add the entries from persisted data. for (std::vector<std::string>::reverse_iterator it = spdy_servers->rbegin(); it != spdy_servers->rend(); ++it) { spdy_servers_map_.Put(*it, support_spdy); } } void HttpServerPropertiesImpl::InitializeAlternateProtocolServers( AlternateProtocolMap* alternate_protocol_map) { // Keep all the ALTERNATE_PROTOCOL_BROKEN ones since those don't // get persisted. for (AlternateProtocolMap::iterator it = alternate_protocol_map_.begin(); it != alternate_protocol_map_.end();) { AlternateProtocolMap::iterator old_it = it; ++it; if (old_it->second.protocol != ALTERNATE_PROTOCOL_BROKEN) { alternate_protocol_map_.Erase(old_it); } } // Add the entries from persisted data. for (AlternateProtocolMap::reverse_iterator it = alternate_protocol_map->rbegin(); it != alternate_protocol_map->rend(); ++it) { alternate_protocol_map_.Put(it->first, it->second); } // Attempt to find canonical servers. int canonical_ports[] = { 80, 443 }; for (size_t i = 0; i < canoncial_suffixes_.size(); ++i) { std::string canonical_suffix = canoncial_suffixes_[i]; for (size_t j = 0; j < arraysize(canonical_ports); ++j) { HostPortPair canonical_host(canonical_suffix, canonical_ports[j]); // If we already have a valid canonical server, we're done. if (ContainsKey(canonical_host_to_origin_map_, canonical_host) && (alternate_protocol_map_.Peek(canonical_host_to_origin_map_[ canonical_host]) != alternate_protocol_map_.end())) { continue; } // Now attempt to find a server which matches this origin and set it as // canonical . for (AlternateProtocolMap::const_iterator it = alternate_protocol_map_.begin(); it != alternate_protocol_map_.end(); ++it) { if (EndsWith(it->first.host(), canoncial_suffixes_[i], false)) { canonical_host_to_origin_map_[canonical_host] = it->first; break; } } } } } void HttpServerPropertiesImpl::InitializeSpdySettingsServers( SpdySettingsMap* spdy_settings_map) { for (SpdySettingsMap::reverse_iterator it = spdy_settings_map->rbegin(); it != spdy_settings_map->rend(); ++it) { spdy_settings_map_.Put(it->first, it->second); } } void HttpServerPropertiesImpl::GetSpdyServerList( base::ListValue* spdy_server_list, size_t max_size) const { DCHECK(CalledOnValidThread()); DCHECK(spdy_server_list); spdy_server_list->Clear(); size_t count = 0; // Get the list of servers (host/port) that support SPDY. for (SpdyServerHostPortMap::const_iterator it = spdy_servers_map_.begin(); it != spdy_servers_map_.end() && count < max_size; ++it) { const std::string spdy_server_host_port = it->first; if (it->second) { spdy_server_list->Append(new base::StringValue(spdy_server_host_port)); ++count; } } } // static std::string HttpServerPropertiesImpl::GetFlattenedSpdyServer( const net::HostPortPair& host_port_pair) { std::string spdy_server; spdy_server.append(host_port_pair.host()); spdy_server.append(":"); base::StringAppendF(&spdy_server, "%d", host_port_pair.port()); return spdy_server; } static const PortAlternateProtocolPair* g_forced_alternate_protocol = NULL; // static void HttpServerPropertiesImpl::ForceAlternateProtocol( const PortAlternateProtocolPair& pair) { // Note: we're going to leak this. if (g_forced_alternate_protocol) delete g_forced_alternate_protocol; g_forced_alternate_protocol = new PortAlternateProtocolPair(pair); } // static void HttpServerPropertiesImpl::DisableForcedAlternateProtocol() { delete g_forced_alternate_protocol; g_forced_alternate_protocol = NULL; } base::WeakPtr<HttpServerProperties> HttpServerPropertiesImpl::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } void HttpServerPropertiesImpl::Clear() { DCHECK(CalledOnValidThread()); spdy_servers_map_.Clear(); alternate_protocol_map_.Clear(); spdy_settings_map_.Clear(); } bool HttpServerPropertiesImpl::SupportsSpdy( const net::HostPortPair& host_port_pair) { DCHECK(CalledOnValidThread()); if (host_port_pair.host().empty()) return false; std::string spdy_server = GetFlattenedSpdyServer(host_port_pair); SpdyServerHostPortMap::iterator spdy_host_port = spdy_servers_map_.Get(spdy_server); if (spdy_host_port != spdy_servers_map_.end()) return spdy_host_port->second; return false; } void HttpServerPropertiesImpl::SetSupportsSpdy( const net::HostPortPair& host_port_pair, bool support_spdy) { DCHECK(CalledOnValidThread()); if (host_port_pair.host().empty()) return; std::string spdy_server = GetFlattenedSpdyServer(host_port_pair); SpdyServerHostPortMap::iterator spdy_host_port = spdy_servers_map_.Get(spdy_server); if ((spdy_host_port != spdy_servers_map_.end()) && (spdy_host_port->second == support_spdy)) { return; } // Cache the data. spdy_servers_map_.Put(spdy_server, support_spdy); } bool HttpServerPropertiesImpl::HasAlternateProtocol( const HostPortPair& server) { if (alternate_protocol_map_.Get(server) != alternate_protocol_map_.end() || g_forced_alternate_protocol) return true; return GetCanonicalHost(server) != canonical_host_to_origin_map_.end(); } std::string HttpServerPropertiesImpl::GetCanonicalSuffix( const HostPortPair& server) { // If this host ends with a canonical suffix, then return the canonical // suffix. for (size_t i = 0; i < canoncial_suffixes_.size(); ++i) { std::string canonical_suffix = canoncial_suffixes_[i]; if (EndsWith(server.host(), canoncial_suffixes_[i], false)) { return canonical_suffix; } } return std::string(); } PortAlternateProtocolPair HttpServerPropertiesImpl::GetAlternateProtocol( const HostPortPair& server) { DCHECK(HasAlternateProtocol(server)); // First check the map. AlternateProtocolMap::iterator it = alternate_protocol_map_.Get(server); if (it != alternate_protocol_map_.end()) return it->second; // Next check the canonical host. CanonicalHostMap::const_iterator canonical_host = GetCanonicalHost(server); if (canonical_host != canonical_host_to_origin_map_.end()) return alternate_protocol_map_.Get(canonical_host->second)->second; // We must be forcing an alternate. DCHECK(g_forced_alternate_protocol); return *g_forced_alternate_protocol; } void HttpServerPropertiesImpl::SetAlternateProtocol( const HostPortPair& server, uint16 alternate_port, AlternateProtocol alternate_protocol) { if (alternate_protocol == ALTERNATE_PROTOCOL_BROKEN) { LOG(DFATAL) << "Call SetBrokenAlternateProtocol() instead."; return; } PortAlternateProtocolPair alternate; alternate.port = alternate_port; alternate.protocol = alternate_protocol; if (HasAlternateProtocol(server)) { const PortAlternateProtocolPair existing_alternate = GetAlternateProtocol(server); if (existing_alternate.protocol == ALTERNATE_PROTOCOL_BROKEN) { DVLOG(1) << "Ignore alternate protocol since it's known to be broken."; return; } if (alternate_protocol != ALTERNATE_PROTOCOL_BROKEN && !existing_alternate.Equals(alternate)) { LOG(WARNING) << "Changing the alternate protocol for: " << server.ToString() << " from [Port: " << existing_alternate.port << ", Protocol: " << existing_alternate.protocol << "] to [Port: " << alternate_port << ", Protocol: " << alternate_protocol << "]."; } } else { // TODO(rch): Consider the case where multiple requests are started // before the first completes. In this case, only one of the jobs // would reach this code, whereas all of them should should have. HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_MAPPING_MISSING, alternate_protocol_experiment_); } alternate_protocol_map_.Put(server, alternate); // If this host ends with a canonical suffix, then set it as the // canonical host. for (size_t i = 0; i < canoncial_suffixes_.size(); ++i) { std::string canonical_suffix = canoncial_suffixes_[i]; if (EndsWith(server.host(), canoncial_suffixes_[i], false)) { HostPortPair canonical_host(canonical_suffix, server.port()); canonical_host_to_origin_map_[canonical_host] = server; break; } } } void HttpServerPropertiesImpl::SetBrokenAlternateProtocol( const HostPortPair& server) { AlternateProtocolMap::iterator it = alternate_protocol_map_.Get(server); if (it != alternate_protocol_map_.end()) { it->second.protocol = ALTERNATE_PROTOCOL_BROKEN; } else { PortAlternateProtocolPair alternate; alternate.protocol = ALTERNATE_PROTOCOL_BROKEN; alternate_protocol_map_.Put(server, alternate); } int count = ++broken_alternate_protocol_map_[server]; base::TimeDelta delay = base::TimeDelta::FromSeconds(kBrokenAlternateProtocolDelaySecs); BrokenAlternateProtocolEntry entry; entry.server = server; entry.when = base::TimeTicks::Now() + delay * (1 << (count - 1)); broken_alternate_protocol_list_.push_back(entry); // If this is the only entry in the list, schedule an expiration task. // Otherwse it will be rescheduled automatically when the pending // task runs. if (broken_alternate_protocol_list_.size() == 1) { ScheduleBrokenAlternateProtocolMappingsExpiration(); } } bool HttpServerPropertiesImpl::WasAlternateProtocolRecentlyBroken( const HostPortPair& server) { return ContainsKey(broken_alternate_protocol_map_, server); } void HttpServerPropertiesImpl::ConfirmAlternateProtocol( const HostPortPair& server) { broken_alternate_protocol_map_.erase(server); } void HttpServerPropertiesImpl::ClearAlternateProtocol( const HostPortPair& server) { AlternateProtocolMap::iterator it = alternate_protocol_map_.Peek(server); if (it != alternate_protocol_map_.end()) alternate_protocol_map_.Erase(it); } const AlternateProtocolMap& HttpServerPropertiesImpl::alternate_protocol_map() const { return alternate_protocol_map_; } void HttpServerPropertiesImpl::SetAlternateProtocolExperiment( AlternateProtocolExperiment experiment) { alternate_protocol_experiment_ = experiment; } AlternateProtocolExperiment HttpServerPropertiesImpl::GetAlternateProtocolExperiment() const { return alternate_protocol_experiment_; } const SettingsMap& HttpServerPropertiesImpl::GetSpdySettings( const HostPortPair& host_port_pair) { SpdySettingsMap::iterator it = spdy_settings_map_.Get(host_port_pair); if (it == spdy_settings_map_.end()) { CR_DEFINE_STATIC_LOCAL(SettingsMap, kEmptySettingsMap, ()); return kEmptySettingsMap; } return it->second; } bool HttpServerPropertiesImpl::SetSpdySetting( const HostPortPair& host_port_pair, SpdySettingsIds id, SpdySettingsFlags flags, uint32 value) { if (!(flags & SETTINGS_FLAG_PLEASE_PERSIST)) return false; SettingsFlagsAndValue flags_and_value(SETTINGS_FLAG_PERSISTED, value); SpdySettingsMap::iterator it = spdy_settings_map_.Get(host_port_pair); if (it == spdy_settings_map_.end()) { SettingsMap settings_map; settings_map[id] = flags_and_value; spdy_settings_map_.Put(host_port_pair, settings_map); } else { SettingsMap& settings_map = it->second; settings_map[id] = flags_and_value; } return true; } void HttpServerPropertiesImpl::ClearSpdySettings( const HostPortPair& host_port_pair) { SpdySettingsMap::iterator it = spdy_settings_map_.Peek(host_port_pair); if (it != spdy_settings_map_.end()) spdy_settings_map_.Erase(it); } void HttpServerPropertiesImpl::ClearAllSpdySettings() { spdy_settings_map_.Clear(); } const SpdySettingsMap& HttpServerPropertiesImpl::spdy_settings_map() const { return spdy_settings_map_; } void HttpServerPropertiesImpl::SetServerNetworkStats( const HostPortPair& host_port_pair, NetworkStats stats) { server_network_stats_map_[host_port_pair] = stats; } const HttpServerProperties::NetworkStats* HttpServerPropertiesImpl::GetServerNetworkStats( const HostPortPair& host_port_pair) const { ServerNetworkStatsMap::const_iterator it = server_network_stats_map_.find(host_port_pair); if (it == server_network_stats_map_.end()) { return NULL; } return &it->second; } HttpServerPropertiesImpl::CanonicalHostMap::const_iterator HttpServerPropertiesImpl::GetCanonicalHost(HostPortPair server) const { for (size_t i = 0; i < canoncial_suffixes_.size(); ++i) { std::string canonical_suffix = canoncial_suffixes_[i]; if (EndsWith(server.host(), canoncial_suffixes_[i], false)) { HostPortPair canonical_host(canonical_suffix, server.port()); return canonical_host_to_origin_map_.find(canonical_host); } } return canonical_host_to_origin_map_.end(); } void HttpServerPropertiesImpl::ExpireBrokenAlternateProtocolMappings() { base::TimeTicks now = base::TimeTicks::Now(); while (!broken_alternate_protocol_list_.empty()) { BrokenAlternateProtocolEntry entry = broken_alternate_protocol_list_.front(); if (now < entry.when) { break; } ClearAlternateProtocol(entry.server); broken_alternate_protocol_list_.pop_front(); } ScheduleBrokenAlternateProtocolMappingsExpiration(); } void HttpServerPropertiesImpl::ScheduleBrokenAlternateProtocolMappingsExpiration() { if (broken_alternate_protocol_list_.empty()) { return; } base::TimeTicks now = base::TimeTicks::Now(); base::TimeTicks when = broken_alternate_protocol_list_.front().when; base::TimeDelta delay = when > now ? when - now : base::TimeDelta(); base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind( &HttpServerPropertiesImpl::ExpireBrokenAlternateProtocolMappings, weak_ptr_factory_.GetWeakPtr()), delay); } } // namespace net