1// Copyright 2014 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#include "net/spdy/hpack_huffman_aggregator.h"
5
6#include "base/metrics/bucket_ranges.h"
7#include "base/metrics/field_trial.h"
8#include "base/metrics/histogram.h"
9#include "base/metrics/sample_vector.h"
10#include "base/stl_util.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/string_util.h"
13#include "net/base/load_flags.h"
14#include "net/http/http_request_headers.h"
15#include "net/http/http_request_info.h"
16#include "net/http/http_response_headers.h"
17#include "net/spdy/hpack_encoder.h"
18#include "net/spdy/spdy_http_utils.h"
19
20namespace net {
21
22namespace {
23
24const char kHistogramName[] = "Net.SpdyHpackEncodedCharacterFrequency";
25
26const size_t kTotalCountsPublishThreshold = 50000;
27
28// Each encoder uses the default dynamic table size of 4096 total bytes.
29const size_t kMaxEncoders = 20;
30
31}  // namespace
32
33HpackHuffmanAggregator::HpackHuffmanAggregator()
34  : counts_(256, 0),
35    total_counts_(0),
36    max_encoders_(kMaxEncoders) {
37}
38
39HpackHuffmanAggregator::~HpackHuffmanAggregator() {
40  STLDeleteContainerPairSecondPointers(encoders_.begin(), encoders_.end());
41  encoders_.clear();
42}
43
44void HpackHuffmanAggregator::AggregateTransactionCharacterCounts(
45    const HttpRequestInfo& request,
46    const HttpRequestHeaders& request_headers,
47    const ProxyServer& proxy,
48    const HttpResponseHeaders& response_headers) {
49  if (IsCrossOrigin(request)) {
50    return;
51  }
52  HostPortPair endpoint = HostPortPair(request.url.HostNoBrackets(),
53                                       request.url.EffectiveIntPort());
54  HpackEncoder* encoder = ObtainEncoder(
55      SpdySessionKey(endpoint, proxy, request.privacy_mode));
56
57  // Convert and encode the request and response header sets.
58  {
59    SpdyHeaderBlock headers;
60    CreateSpdyHeadersFromHttpRequest(
61        request, request_headers, SPDY4, false, &headers);
62
63    std::string tmp_out;
64    encoder->EncodeHeaderSet(headers, &tmp_out);
65  }
66  {
67    SpdyHeaderBlock headers;
68    CreateSpdyHeadersFromHttpResponse(response_headers, &headers);
69
70    std::string tmp_out;
71    encoder->EncodeHeaderSet(headers, &tmp_out);
72  }
73  if (total_counts_ >= kTotalCountsPublishThreshold) {
74    PublishCounts();
75  }
76}
77
78// static
79bool HpackHuffmanAggregator::UseAggregator() {
80  const std::string group_name =
81      base::FieldTrialList::FindFullName("HpackHuffmanAggregator");
82  if (group_name == "Enabled") {
83    return true;
84  }
85  return false;
86}
87
88// static
89void HpackHuffmanAggregator::CreateSpdyHeadersFromHttpResponse(
90    const HttpResponseHeaders& headers,
91    SpdyHeaderBlock* headers_out) {
92  // Lower-case header names, and coalesce multiple values delimited by \0.
93  // Also add the fixed status header.
94  std::string name, value;
95  void* it = NULL;
96  while (headers.EnumerateHeaderLines(&it, &name, &value)) {
97    base::StringToLowerASCII(&name);
98    if (headers_out->find(name) == headers_out->end()) {
99      (*headers_out)[name] = value;
100    } else {
101      (*headers_out)[name] += std::string(1, '\0') + value;
102    }
103  }
104  (*headers_out)[":status"] = base::IntToString(headers.response_code());
105}
106
107// static
108bool HpackHuffmanAggregator::IsCrossOrigin(const HttpRequestInfo& request) {
109  // Require that the request is top-level, or that it shares
110  // an origin with its referer.
111  HostPortPair endpoint = HostPortPair(request.url.HostNoBrackets(),
112                                       request.url.EffectiveIntPort());
113  if ((request.load_flags & LOAD_MAIN_FRAME) == 0) {
114    std::string referer_str;
115    if (!request.extra_headers.GetHeader(HttpRequestHeaders::kReferer,
116                                         &referer_str)) {
117      // Require a referer.
118      return true;
119    }
120    GURL referer(referer_str);
121    HostPortPair referer_endpoint = HostPortPair(referer.HostNoBrackets(),
122                                                 referer.EffectiveIntPort());
123    if (!endpoint.Equals(referer_endpoint)) {
124      // Cross-origin request.
125      return true;
126    }
127  }
128  return false;
129}
130
131HpackEncoder* HpackHuffmanAggregator::ObtainEncoder(const SpdySessionKey& key) {
132  for (OriginEncoders::iterator it = encoders_.begin();
133       it != encoders_.end(); ++it) {
134    if (key.Equals(it->first)) {
135      // Move to head of list and return.
136      OriginEncoder origin_encoder = *it;
137      encoders_.erase(it);
138      encoders_.push_front(origin_encoder);
139      return origin_encoder.second;
140    }
141  }
142  // Not found. Create a new encoder, evicting one if needed.
143  encoders_.push_front(std::make_pair(
144      key, new HpackEncoder(ObtainHpackHuffmanTable())));
145  if (encoders_.size() > max_encoders_) {
146    delete encoders_.back().second;
147    encoders_.pop_back();
148  }
149  encoders_.front().second->SetCharCountsStorage(&counts_, &total_counts_);
150  return encoders_.front().second;
151}
152
153void HpackHuffmanAggregator::PublishCounts() {
154  // base::Histogram requires that values be 1-indexed.
155  const size_t kRangeMin = 1;
156  const size_t kRangeMax = counts_.size() + 1;
157  const size_t kBucketCount = kRangeMax + 1;
158
159  base::BucketRanges ranges(kBucketCount + 1);
160  for (size_t i = 0; i != ranges.size(); ++i) {
161    ranges.set_range(i, i);
162  }
163  ranges.ResetChecksum();
164
165  // Copy |counts_| into a SampleVector.
166  base::SampleVector samples(&ranges);
167  for (size_t i = 0; i != counts_.size(); ++i) {
168    samples.Accumulate(i + 1, counts_[i]);
169  }
170
171  STATIC_HISTOGRAM_POINTER_BLOCK(
172      kHistogramName,
173      AddSamples(samples),
174      base::LinearHistogram::FactoryGet(
175          kHistogramName, kRangeMin, kRangeMax, kBucketCount,
176          base::HistogramBase::kUmaTargetedHistogramFlag));
177
178  // Clear counts.
179  counts_.assign(counts_.size(), 0);
180  total_counts_ = 0;
181}
182
183}  // namespace net
184