blob: 5b2c2f12caa00b8c2a0f43b83ef001f5c0e02e5a [file] [log] [blame]
// 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/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
namespace net {
namespace {
const uint64 kBrokenAlternativeProtocolDelaySecs = 300;
} // namespace
HttpServerPropertiesImpl::HttpServerPropertiesImpl()
: spdy_servers_map_(SpdyServerHostPortMap::NO_AUTO_EVICT),
alternative_service_map_(AlternativeServiceMap::NO_AUTO_EVICT),
spdy_settings_map_(SpdySettingsMap::NO_AUTO_EVICT),
server_network_stats_map_(ServerNetworkStatsMap::NO_AUTO_EVICT),
alternate_protocol_probability_threshold_(1),
weak_ptr_factory_(this) {
canonical_suffixes_.push_back(".c.youtube.com");
canonical_suffixes_.push_back(".googlevideo.com");
canonical_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::InitializeAlternativeServiceServers(
AlternativeServiceMap* alternative_service_map) {
// Keep all the broken ones since those don't get persisted.
for (AlternativeServiceMap::iterator it = alternative_service_map_.begin();
it != alternative_service_map_.end();) {
if (IsAlternativeServiceBroken(it->second.alternative_service)) {
++it;
} else {
it = alternative_service_map_.Erase(it);
}
}
// Add the entries from persisted data.
for (AlternativeServiceMap::reverse_iterator it =
alternative_service_map->rbegin();
it != alternative_service_map->rend(); ++it) {
alternative_service_map_.Put(it->first, it->second);
}
// Attempt to find canonical servers.
uint16 canonical_ports[] = { 80, 443 };
for (size_t i = 0; i < canonical_suffixes_.size(); ++i) {
std::string canonical_suffix = canonical_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) &&
(alternative_service_map_.Peek(
canonical_host_to_origin_map_[canonical_host]) !=
alternative_service_map_.end())) {
continue;
}
// Now attempt to find a server which matches this origin and set it as
// canonical.
for (AlternativeServiceMap::const_iterator it =
alternative_service_map_.begin();
it != alternative_service_map_.end(); ++it) {
if (EndsWith(it->first.host(), canonical_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::InitializeSupportsQuic(
IPAddressNumber* last_address) {
if (last_address)
last_quic_address_ = *last_address;
}
void HttpServerPropertiesImpl::InitializeServerNetworkStats(
ServerNetworkStatsMap* server_network_stats_map) {
for (ServerNetworkStatsMap::reverse_iterator it =
server_network_stats_map->rbegin();
it != server_network_stats_map->rend(); ++it) {
server_network_stats_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;
}
}
}
base::WeakPtr<HttpServerProperties> HttpServerPropertiesImpl::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void HttpServerPropertiesImpl::Clear() {
DCHECK(CalledOnValidThread());
spdy_servers_map_.Clear();
alternative_service_map_.Clear();
canonical_host_to_origin_map_.clear();
spdy_settings_map_.Clear();
last_quic_address_.clear();
server_network_stats_map_.Clear();
}
bool HttpServerPropertiesImpl::SupportsRequestPriority(
const HostPortPair& host_port_pair) {
DCHECK(CalledOnValidThread());
if (host_port_pair.host().empty())
return false;
SpdyServerHostPortMap::iterator spdy_host_port =
spdy_servers_map_.Get(host_port_pair.ToString());
if (spdy_host_port != spdy_servers_map_.end() && spdy_host_port->second)
return true;
const AlternativeService alternative_service =
GetAlternativeService(host_port_pair);
return alternative_service.protocol == QUIC;
}
void HttpServerPropertiesImpl::SetSupportsSpdy(
const HostPortPair& host_port_pair,
bool support_spdy) {
DCHECK(CalledOnValidThread());
if (host_port_pair.host().empty())
return;
SpdyServerHostPortMap::iterator spdy_host_port =
spdy_servers_map_.Get(host_port_pair.ToString());
if ((spdy_host_port != spdy_servers_map_.end()) &&
(spdy_host_port->second == support_spdy)) {
return;
}
// Cache the data.
spdy_servers_map_.Put(host_port_pair.ToString(), support_spdy);
}
bool HttpServerPropertiesImpl::RequiresHTTP11(
const net::HostPortPair& host_port_pair) {
DCHECK(CalledOnValidThread());
if (host_port_pair.host().empty())
return false;
return (http11_servers_.find(host_port_pair) != http11_servers_.end());
}
void HttpServerPropertiesImpl::SetHTTP11Required(
const net::HostPortPair& host_port_pair) {
DCHECK(CalledOnValidThread());
if (host_port_pair.host().empty())
return;
http11_servers_.insert(host_port_pair);
}
void HttpServerPropertiesImpl::MaybeForceHTTP11(const HostPortPair& server,
SSLConfig* ssl_config) {
if (RequiresHTTP11(server)) {
ForceHTTP11(ssl_config);
}
}
std::string HttpServerPropertiesImpl::GetCanonicalSuffix(
const std::string& host) {
// If this host ends with a canonical suffix, then return the canonical
// suffix.
for (size_t i = 0; i < canonical_suffixes_.size(); ++i) {
std::string canonical_suffix = canonical_suffixes_[i];
if (EndsWith(host, canonical_suffixes_[i], false)) {
return canonical_suffix;
}
}
return std::string();
}
AlternativeService HttpServerPropertiesImpl::GetAlternativeService(
const HostPortPair& origin) {
AlternativeServiceMap::const_iterator it =
alternative_service_map_.Get(origin);
if (it != alternative_service_map_.end()) {
if (it->second.probability < alternate_protocol_probability_threshold_) {
return AlternativeService();
}
AlternativeService alternative_service(it->second.alternative_service);
if (alternative_service.host.empty()) {
alternative_service.host = origin.host();
}
return alternative_service;
}
CanonicalHostMap::const_iterator canonical = GetCanonicalHost(origin);
if (canonical == canonical_host_to_origin_map_.end()) {
return AlternativeService();
}
it = alternative_service_map_.Get(canonical->second);
if (it == alternative_service_map_.end()) {
return AlternativeService();
}
if (it->second.probability < alternate_protocol_probability_threshold_) {
return AlternativeService();
}
AlternativeService alternative_service(it->second.alternative_service);
if (alternative_service.host.empty()) {
alternative_service.host = canonical->second.host();
}
if (IsAlternativeServiceBroken(alternative_service)) {
RemoveCanonicalHost(canonical->second);
return AlternativeService();
}
// Empty hostname: if alternative service for with hostname of canonical host
// is not broken, then return alternative service with hostname of origin.
if (it->second.alternative_service.host.empty()) {
alternative_service.host = origin.host();
}
return alternative_service;
}
void HttpServerPropertiesImpl::SetAlternativeService(
const HostPortPair& origin,
const AlternativeService& alternative_service,
double alternative_probability) {
AlternativeService complete_alternative_service(alternative_service);
if (complete_alternative_service.host.empty()) {
complete_alternative_service.host = origin.host();
}
if (IsAlternativeServiceBroken(complete_alternative_service)) {
DVLOG(1) << "Ignore alternative service since it is known to be broken.";
return;
}
const AlternativeServiceInfo alternative_service_info(
alternative_service, alternative_probability);
AlternativeServiceMap::const_iterator it =
GetAlternateProtocolIterator(origin);
if (it != alternative_service_map_.end()) {
const AlternativeServiceInfo existing_alternative_service_info = it->second;
if (existing_alternative_service_info != alternative_service_info) {
LOG(WARNING) << "Changing the alternative service for: "
<< origin.ToString() << " from "
<< existing_alternative_service_info.ToString() << " to "
<< alternative_service_info.ToString() << ".";
}
} else {
if (alternative_probability >= alternate_protocol_probability_threshold_) {
// 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);
}
}
alternative_service_map_.Put(origin, alternative_service_info);
// If this host ends with a canonical suffix, then set it as the
// canonical host.
for (size_t i = 0; i < canonical_suffixes_.size(); ++i) {
std::string canonical_suffix = canonical_suffixes_[i];
if (EndsWith(origin.host(), canonical_suffixes_[i], false)) {
HostPortPair canonical_host(canonical_suffix, origin.port());
canonical_host_to_origin_map_[canonical_host] = origin;
break;
}
}
}
void HttpServerPropertiesImpl::MarkAlternativeServiceBroken(
const AlternativeService& alternative_service) {
if (alternative_service.protocol == UNINITIALIZED_ALTERNATE_PROTOCOL) {
LOG(DFATAL) << "Trying to mark unknown alternate protocol broken.";
return;
}
int count = ++recently_broken_alternative_services_[alternative_service];
base::TimeDelta delay =
base::TimeDelta::FromSeconds(kBrokenAlternativeProtocolDelaySecs);
base::TimeTicks when = base::TimeTicks::Now() + delay * (1 << (count - 1));
auto result = broken_alternative_services_.insert(
std::make_pair(alternative_service, when));
// Return if alternative service is already in expiration queue.
if (!result.second) {
return;
}
// If this is the only entry in the list, schedule an expiration task.
// Otherwise it will be rescheduled automatically when the pending task runs.
if (broken_alternative_services_.size() == 1) {
ScheduleBrokenAlternateProtocolMappingsExpiration();
}
}
void HttpServerPropertiesImpl::MarkAlternativeServiceRecentlyBroken(
const AlternativeService& alternative_service) {
if (!ContainsKey(recently_broken_alternative_services_, alternative_service))
recently_broken_alternative_services_[alternative_service] = 1;
}
bool HttpServerPropertiesImpl::IsAlternativeServiceBroken(
const AlternativeService& alternative_service) {
// Empty host means use host of origin, callers are supposed to substitute.
DCHECK(!alternative_service.host.empty());
return ContainsKey(broken_alternative_services_, alternative_service);
}
bool HttpServerPropertiesImpl::WasAlternativeServiceRecentlyBroken(
const AlternativeService& alternative_service) {
if (alternative_service.protocol == UNINITIALIZED_ALTERNATE_PROTOCOL)
return false;
return ContainsKey(recently_broken_alternative_services_,
alternative_service);
}
void HttpServerPropertiesImpl::ConfirmAlternativeService(
const AlternativeService& alternative_service) {
if (alternative_service.protocol == UNINITIALIZED_ALTERNATE_PROTOCOL)
return;
broken_alternative_services_.erase(alternative_service);
recently_broken_alternative_services_.erase(alternative_service);
}
void HttpServerPropertiesImpl::ClearAlternativeService(
const HostPortPair& origin) {
RemoveCanonicalHost(origin);
AlternativeServiceMap::iterator it = alternative_service_map_.Peek(origin);
if (it == alternative_service_map_.end()) {
return;
}
AlternativeService alternative_service(it->second.alternative_service);
if (alternative_service.host.empty()) {
alternative_service.host = origin.host();
}
alternative_service_map_.Erase(it);
// The following is temporary to keep the existing semantics, which is that if
// there is a broken alternative service in the mapping, then this method
// leaves it in a non-broken, but recently broken state.
//
// TODO(bnc):
// 1. Verify and document the class invariant that no broken alternative
// service can be in the mapping.
// 2. Remove the rest of this method as it will be moot.
broken_alternative_services_.erase(alternative_service);
}
const AlternativeServiceMap& HttpServerPropertiesImpl::alternative_service_map()
const {
return alternative_service_map_;
}
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_;
}
bool HttpServerPropertiesImpl::GetSupportsQuic(
IPAddressNumber* last_address) const {
if (last_quic_address_.empty())
return false;
*last_address = last_quic_address_;
return true;
}
void HttpServerPropertiesImpl::SetSupportsQuic(bool used_quic,
const IPAddressNumber& address) {
if (!used_quic) {
last_quic_address_.clear();
} else {
last_quic_address_ = address;
}
}
void HttpServerPropertiesImpl::SetServerNetworkStats(
const HostPortPair& host_port_pair,
ServerNetworkStats stats) {
server_network_stats_map_.Put(host_port_pair, stats);
}
const ServerNetworkStats* HttpServerPropertiesImpl::GetServerNetworkStats(
const HostPortPair& host_port_pair) {
ServerNetworkStatsMap::iterator it =
server_network_stats_map_.Get(host_port_pair);
if (it == server_network_stats_map_.end()) {
return NULL;
}
return &it->second;
}
const ServerNetworkStatsMap&
HttpServerPropertiesImpl::server_network_stats_map() const {
return server_network_stats_map_;
}
void HttpServerPropertiesImpl::SetAlternateProtocolProbabilityThreshold(
double threshold) {
alternate_protocol_probability_threshold_ = threshold;
}
AlternativeServiceMap::const_iterator
HttpServerPropertiesImpl::GetAlternateProtocolIterator(
const HostPortPair& server) {
AlternativeServiceMap::const_iterator it =
alternative_service_map_.Get(server);
if (it != alternative_service_map_.end())
return it;
CanonicalHostMap::const_iterator canonical = GetCanonicalHost(server);
if (canonical == canonical_host_to_origin_map_.end()) {
return alternative_service_map_.end();
}
const HostPortPair canonical_host_port = canonical->second;
it = alternative_service_map_.Get(canonical_host_port);
if (it == alternative_service_map_.end()) {
return alternative_service_map_.end();
}
const AlternativeService alternative_service(
it->second.alternative_service.protocol, canonical_host_port.host(),
it->second.alternative_service.port);
if (!IsAlternativeServiceBroken(alternative_service)) {
return it;
}
RemoveCanonicalHost(canonical_host_port);
return alternative_service_map_.end();
}
HttpServerPropertiesImpl::CanonicalHostMap::const_iterator
HttpServerPropertiesImpl::GetCanonicalHost(HostPortPair server) const {
for (size_t i = 0; i < canonical_suffixes_.size(); ++i) {
std::string canonical_suffix = canonical_suffixes_[i];
if (EndsWith(server.host(), canonical_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::RemoveCanonicalHost(
const HostPortPair& server) {
CanonicalHostMap::const_iterator canonical = GetCanonicalHost(server);
if (canonical == canonical_host_to_origin_map_.end())
return;
if (!canonical->second.Equals(server))
return;
canonical_host_to_origin_map_.erase(canonical->first);
}
void HttpServerPropertiesImpl::ExpireBrokenAlternateProtocolMappings() {
base::TimeTicks now = base::TimeTicks::Now();
while (!broken_alternative_services_.empty()) {
BrokenAlternativeServices::iterator it =
broken_alternative_services_.begin();
if (now < it->second) {
break;
}
const AlternativeService alternative_service = it->first;
broken_alternative_services_.erase(it);
// TODO(bnc): Make sure broken alternative services are not in the mapping.
ClearAlternativeService(
HostPortPair(alternative_service.host, alternative_service.port));
}
ScheduleBrokenAlternateProtocolMappingsExpiration();
}
void
HttpServerPropertiesImpl::ScheduleBrokenAlternateProtocolMappingsExpiration() {
if (broken_alternative_services_.empty()) {
return;
}
base::TimeTicks now = base::TimeTicks::Now();
base::TimeTicks when = broken_alternative_services_.front().second;
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