| // Copyright 2014 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/spdy/hpack_huffman_aggregator.h" |
| |
| #include "base/metrics/bucket_ranges.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/sample_vector.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_request_info.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/spdy/hpack_encoder.h" |
| #include "net/spdy/spdy_http_utils.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| const char kHistogramName[] = "Net.SpdyHpackEncodedCharacterFrequency"; |
| |
| const size_t kTotalCountsPublishThreshold = 50000; |
| |
| // Each encoder uses the default dynamic table size of 4096 total bytes. |
| const size_t kMaxEncoders = 20; |
| |
| } // namespace |
| |
| HpackHuffmanAggregator::HpackHuffmanAggregator() |
| : counts_(256, 0), |
| total_counts_(0), |
| max_encoders_(kMaxEncoders) { |
| } |
| |
| HpackHuffmanAggregator::~HpackHuffmanAggregator() { |
| STLDeleteContainerPairSecondPointers(encoders_.begin(), encoders_.end()); |
| encoders_.clear(); |
| } |
| |
| void HpackHuffmanAggregator::AggregateTransactionCharacterCounts( |
| const HttpRequestInfo& request, |
| const HttpRequestHeaders& request_headers, |
| const ProxyServer& proxy, |
| const HttpResponseHeaders& response_headers) { |
| if (IsCrossOrigin(request)) { |
| return; |
| } |
| HostPortPair endpoint = HostPortPair(request.url.HostNoBrackets(), |
| request.url.EffectiveIntPort()); |
| HpackEncoder* encoder = ObtainEncoder( |
| SpdySessionKey(endpoint, proxy, request.privacy_mode)); |
| |
| // Convert and encode the request and response header sets. |
| { |
| SpdyHeaderBlock headers; |
| CreateSpdyHeadersFromHttpRequest( |
| request, request_headers, &headers, SPDY4, false); |
| |
| std::string tmp_out; |
| encoder->EncodeHeaderSet(headers, &tmp_out); |
| } |
| { |
| SpdyHeaderBlock headers; |
| CreateSpdyHeadersFromHttpResponse(response_headers, &headers); |
| |
| std::string tmp_out; |
| encoder->EncodeHeaderSet(headers, &tmp_out); |
| } |
| if (total_counts_ >= kTotalCountsPublishThreshold) { |
| PublishCounts(); |
| } |
| } |
| |
| // static |
| bool HpackHuffmanAggregator::UseAggregator() { |
| const std::string group_name = |
| base::FieldTrialList::FindFullName("HpackHuffmanAggregator"); |
| if (group_name == "Enabled") { |
| return true; |
| } |
| return false; |
| } |
| |
| // static |
| void HpackHuffmanAggregator::CreateSpdyHeadersFromHttpResponse( |
| const HttpResponseHeaders& headers, |
| SpdyHeaderBlock* headers_out) { |
| // Lower-case header names, and coalesce multiple values delimited by \0. |
| // Also add the fixed status header. |
| std::string name, value; |
| void* it = NULL; |
| while (headers.EnumerateHeaderLines(&it, &name, &value)) { |
| StringToLowerASCII(&name); |
| if (headers_out->find(name) == headers_out->end()) { |
| (*headers_out)[name] = value; |
| } else { |
| (*headers_out)[name] += std::string(1, '\0') + value; |
| } |
| } |
| (*headers_out)[":status"] = base::IntToString(headers.response_code()); |
| } |
| |
| // static |
| bool HpackHuffmanAggregator::IsCrossOrigin(const HttpRequestInfo& request) { |
| // Require that the request is top-level, or that it shares |
| // an origin with its referer. |
| HostPortPair endpoint = HostPortPair(request.url.HostNoBrackets(), |
| request.url.EffectiveIntPort()); |
| if ((request.load_flags & LOAD_MAIN_FRAME) == 0) { |
| std::string referer_str; |
| if (!request.extra_headers.GetHeader(HttpRequestHeaders::kReferer, |
| &referer_str)) { |
| // Require a referer. |
| return true; |
| } |
| GURL referer(referer_str); |
| HostPortPair referer_endpoint = HostPortPair(referer.HostNoBrackets(), |
| referer.EffectiveIntPort()); |
| if (!endpoint.Equals(referer_endpoint)) { |
| // Cross-origin request. |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| HpackEncoder* HpackHuffmanAggregator::ObtainEncoder(const SpdySessionKey& key) { |
| for (OriginEncoders::iterator it = encoders_.begin(); |
| it != encoders_.end(); ++it) { |
| if (key.Equals(it->first)) { |
| // Move to head of list and return. |
| OriginEncoder origin_encoder = *it; |
| encoders_.erase(it); |
| encoders_.push_front(origin_encoder); |
| return origin_encoder.second; |
| } |
| } |
| // Not found. Create a new encoder, evicting one if needed. |
| encoders_.push_front(std::make_pair( |
| key, new HpackEncoder(ObtainHpackHuffmanTable()))); |
| if (encoders_.size() > max_encoders_) { |
| delete encoders_.back().second; |
| encoders_.pop_back(); |
| } |
| encoders_.front().second->SetCharCountsStorage(&counts_, &total_counts_); |
| return encoders_.front().second; |
| } |
| |
| void HpackHuffmanAggregator::PublishCounts() { |
| // base::Histogram requires that values be 1-indexed. |
| const size_t kRangeMin = 1; |
| const size_t kRangeMax = counts_.size() + 1; |
| const size_t kBucketCount = kRangeMax + 1; |
| |
| base::BucketRanges ranges(kBucketCount + 1); |
| for (size_t i = 0; i != ranges.size(); ++i) { |
| ranges.set_range(i, i); |
| } |
| ranges.ResetChecksum(); |
| |
| // Copy |counts_| into a SampleVector. |
| base::SampleVector samples(&ranges); |
| for (size_t i = 0; i != counts_.size(); ++i) { |
| samples.Accumulate(i + 1, counts_[i]); |
| } |
| |
| STATIC_HISTOGRAM_POINTER_BLOCK( |
| kHistogramName, |
| AddSamples(samples), |
| base::LinearHistogram::FactoryGet( |
| kHistogramName, kRangeMin, kRangeMax, kBucketCount, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| |
| // Clear counts. |
| counts_.assign(counts_.size(), 0); |
| total_counts_ = 0; |
| } |
| |
| } // namespace net |