quic_in_memory_cache.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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.
4
5#include "net/tools/quic/quic_in_memory_cache.h"
6
7#include "base/file_util.h"
8#include "base/files/file_enumerator.h"
9#include "base/stl_util.h"
10#include "base/strings/string_number_conversions.h"
11#include "net/tools/balsa/balsa_headers.h"
12
13using base::FilePath;
14using base::StringPiece;
15using std::string;
16
17// Specifies the directory used during QuicInMemoryCache
18// construction to seed the cache. Cache directory can be
19// generated using `wget -p --save-headers <url>
20
21namespace net {
22namespace tools {
23
24std::string FLAGS_quic_in_memory_cache_dir = "";
25
26namespace {
27
28// BalsaVisitor implementation (glue) which caches response bodies.
29class CachingBalsaVisitor : public NoOpBalsaVisitor {
30 public:
31  CachingBalsaVisitor() : done_framing_(false) {}
32  virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE {
33    AppendToBody(input, size);
34  }
35  virtual void MessageDone() OVERRIDE {
36    done_framing_ = true;
37  }
38  virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE {
39    UnhandledError();
40  }
41  virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {
42    UnhandledError();
43  }
44  virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE {
45    UnhandledError();
46  }
47  virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE {
48    UnhandledError();
49  }
50  void UnhandledError() {
51    LOG(DFATAL) << "Unhandled error framing HTTP.";
52  }
53  void AppendToBody(const char* input, size_t size) {
54    body_.append(input, size);
55  }
56  bool done_framing() const { return done_framing_; }
57  const string& body() const { return body_; }
58
59 private:
60  bool done_framing_;
61  string body_;
62};
63
64}  // namespace
65
66// static
67QuicInMemoryCache* QuicInMemoryCache::GetInstance() {
68  return Singleton<QuicInMemoryCache>::get();
69}
70
71const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse(
72    const BalsaHeaders& request_headers) const {
73  ResponseMap::const_iterator it = responses_.find(GetKey(request_headers));
74  if (it == responses_.end()) {
75    return NULL;
76  }
77  return it->second;
78}
79
80void QuicInMemoryCache::AddSimpleResponse(StringPiece method,
81                                          StringPiece path,
82                                          StringPiece version,
83                                          StringPiece response_code,
84                                          StringPiece response_detail,
85                                          StringPiece body) {
86  BalsaHeaders request_headers, response_headers;
87  request_headers.SetRequestFirstlineFromStringPieces(method,
88                                                      path,
89                                                      version);
90  response_headers.SetRequestFirstlineFromStringPieces(version,
91                                                       response_code,
92                                                       response_detail);
93  response_headers.AppendHeader("content-length",
94                                base::IntToString(body.length()));
95
96  AddResponse(request_headers, response_headers, body);
97}
98
99void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers,
100                                    const BalsaHeaders& response_headers,
101                                    StringPiece response_body) {
102  VLOG(1) << "Adding response for: " << GetKey(request_headers);
103  if (ContainsKey(responses_, GetKey(request_headers))) {
104    LOG(DFATAL) << "Response for given request already exists!";
105    return;
106  }
107  Response* new_response = new Response();
108  new_response->set_headers(response_headers);
109  new_response->set_body(response_body);
110  responses_[GetKey(request_headers)] = new_response;
111}
112
113void QuicInMemoryCache::AddSpecialResponse(StringPiece method,
114                                           StringPiece path,
115                                           StringPiece version,
116                                           SpecialResponseType response_type) {
117  BalsaHeaders request_headers, response_headers;
118  request_headers.SetRequestFirstlineFromStringPieces(method,
119                                                      path,
120                                                      version);
121  AddResponse(request_headers, response_headers, "");
122  responses_[GetKey(request_headers)]->response_type_ = response_type;
123}
124
125QuicInMemoryCache::QuicInMemoryCache() {
126  Initialize();
127}
128
129void QuicInMemoryCache::ResetForTests() {
130  STLDeleteValues(&responses_);
131  Initialize();
132}
133
134void QuicInMemoryCache::Initialize() {
135  // If there's no defined cache dir, we have no initialization to do.
136  if (FLAGS_quic_in_memory_cache_dir.empty()) {
137    VLOG(1) << "No cache directory found. Skipping initialization.";
138    return;
139  }
140  VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
141          << FLAGS_quic_in_memory_cache_dir;
142
143  FilePath directory(FLAGS_quic_in_memory_cache_dir);
144  base::FileEnumerator file_list(directory,
145                                 true,
146                                 base::FileEnumerator::FILES);
147
148  FilePath file = file_list.Next();
149  while (!file.empty()) {
150    // Need to skip files in .svn directories
151    if (file.value().find("/.svn/") != std::string::npos) {
152      file = file_list.Next();
153      continue;
154    }
155
156    BalsaHeaders request_headers, response_headers;
157
158    string file_contents;
159    base::ReadFileToString(file, &file_contents);
160
161    // Frame HTTP.
162    CachingBalsaVisitor caching_visitor;
163    BalsaFrame framer;
164    framer.set_balsa_headers(&response_headers);
165    framer.set_balsa_visitor(&caching_visitor);
166    size_t processed = 0;
167    while (processed < file_contents.length() &&
168           !caching_visitor.done_framing()) {
169      processed += framer.ProcessInput(file_contents.c_str() + processed,
170                                       file_contents.length() - processed);
171    }
172
173    if (!caching_visitor.done_framing()) {
174      LOG(DFATAL) << "Did not frame entire message from file: " << file.value()
175                  << " (" << processed << " of " << file_contents.length()
176                  << " bytes).";
177    }
178    if (processed < file_contents.length()) {
179      // Didn't frame whole file. Assume remainder is body.
180      // This sometimes happens as a result of incompatibilities between
181      // BalsaFramer and wget's serialization of HTTP sans content-length.
182      caching_visitor.AppendToBody(file_contents.c_str() + processed,
183                                   file_contents.length() - processed);
184      processed += file_contents.length();
185    }
186
187    StringPiece base = file.value();
188    if (response_headers.HasHeader("X-Original-Url")) {
189      base = response_headers.GetHeader("X-Original-Url");
190      response_headers.RemoveAllOfHeader("X-Original-Url");
191      // Remove the protocol so that the string is of the form host + path,
192      // which is parsed properly below.
193      if (StringPieceUtils::StartsWithIgnoreCase(base, "https://")) {
194        base.remove_prefix(8);
195      } else if (StringPieceUtils::StartsWithIgnoreCase(base, "http://")) {
196        base.remove_prefix(7);
197      }
198    }
199    int path_start = base.find_first_of('/');
200    DCHECK_LT(0, path_start);
201    StringPiece host(base.substr(0, path_start));
202    StringPiece path(base.substr(path_start));
203    if (path[path.length() - 1] == ',') {
204      path.remove_suffix(1);
205    }
206    // Set up request headers. Assume method is GET and protocol is HTTP/1.1.
207    request_headers.SetRequestFirstlineFromStringPieces("GET",
208                                                        path,
209                                                        "HTTP/1.1");
210    request_headers.ReplaceOrAppendHeader("host", host);
211
212    VLOG(1) << "Inserting 'http://" << GetKey(request_headers)
213            << "' into QuicInMemoryCache.";
214
215    AddResponse(request_headers, response_headers, caching_visitor.body());
216
217    file = file_list.Next();
218  }
219}
220
221QuicInMemoryCache::~QuicInMemoryCache() {
222  STLDeleteValues(&responses_);
223}
224
225string QuicInMemoryCache::GetKey(const BalsaHeaders& request_headers) const {
226  StringPiece uri = request_headers.request_uri();
227  if (uri.size() == 0) {
228    return "";
229  }
230  StringPiece host;
231  if (uri[0] == '/') {
232    host = request_headers.GetHeader("host");
233  } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) {
234    uri.remove_prefix(8);
235  } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) {
236    uri.remove_prefix(7);
237  }
238  return host.as_string() + uri.as_string();
239}
240
241}  // namespace tools
242}  // namespace net
243