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