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 #include "base/strings/string_number_conversions.h"
     11 #include "net/tools/balsa/balsa_headers.h"
     12 
     13 using base::FilePath;
     14 using base::StringPiece;
     15 using 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 
     21 namespace net {
     22 namespace tools {
     23 
     24 std::string FLAGS_quic_in_memory_cache_dir = "";
     25 
     26 namespace {
     27 
     28 // BalsaVisitor implementation (glue) which caches response bodies.
     29 class 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
     67 QuicInMemoryCache* QuicInMemoryCache::GetInstance() {
     68   return Singleton<QuicInMemoryCache>::get();
     69 }
     70 
     71 const 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 
     80 void 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 
     99 void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers,
    100                                     const BalsaHeaders& response_headers,
    101                                     StringPiece response_body) {
    102   LOG(INFO) << "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 
    113 QuicInMemoryCache::QuicInMemoryCache() {
    114   Initialize();
    115 }
    116 
    117 void QuicInMemoryCache::ResetForTests() {
    118   STLDeleteValues(&responses_);
    119   Initialize();
    120 }
    121 
    122 void QuicInMemoryCache::Initialize() {
    123   // If there's no defined cache dir, we have no initialization to do.
    124   if (FLAGS_quic_in_memory_cache_dir.empty()) {
    125     LOG(WARNING) << "No cache directory found. Skipping initialization.";
    126     return;
    127   }
    128   LOG(INFO) << "Attempting to initialize QuicInMemoryCache from directory: "
    129             << FLAGS_quic_in_memory_cache_dir;
    130 
    131   FilePath directory(FLAGS_quic_in_memory_cache_dir);
    132   base::FileEnumerator file_list(directory,
    133                                  true,
    134                                  base::FileEnumerator::FILES);
    135 
    136   FilePath file = file_list.Next();
    137   while (!file.empty()) {
    138     // Need to skip files in .svn directories
    139     if (file.value().find("/.svn/") != std::string::npos) {
    140       file = file_list.Next();
    141       continue;
    142     }
    143 
    144     BalsaHeaders request_headers, response_headers;
    145 
    146     string file_contents;
    147     base::ReadFileToString(file, &file_contents);
    148 
    149     // Frame HTTP.
    150     CachingBalsaVisitor caching_visitor;
    151     BalsaFrame framer;
    152     framer.set_balsa_headers(&response_headers);
    153     framer.set_balsa_visitor(&caching_visitor);
    154     size_t processed = 0;
    155     while (processed < file_contents.length() &&
    156            !caching_visitor.done_framing()) {
    157       processed += framer.ProcessInput(file_contents.c_str() + processed,
    158                                        file_contents.length() - processed);
    159     }
    160 
    161     string response_headers_str;
    162     response_headers.DumpToString(&response_headers_str);
    163     if (!caching_visitor.done_framing()) {
    164       LOG(DFATAL) << "Did not frame entire message from file: " << file.value()
    165                   << " (" << processed << " of " << file_contents.length()
    166                   << " bytes).";
    167     }
    168     if (processed < file_contents.length()) {
    169       // Didn't frame whole file. Assume remainder is body.
    170       // This sometimes happens as a result of incompatibilities between
    171       // BalsaFramer and wget's serialization of HTTP sans content-length.
    172       caching_visitor.AppendToBody(file_contents.c_str() + processed,
    173                                    file_contents.length() - processed);
    174       processed += file_contents.length();
    175     }
    176 
    177     StringPiece base = file.value();
    178     if (response_headers.HasHeader("X-Original-Url")) {
    179       base = response_headers.GetHeader("X-Original-Url");
    180       response_headers.RemoveAllOfHeader("X-Original-Url");
    181       // Remove the protocol so that the string is of the form host + path,
    182       // which is parsed properly below.
    183       if (StringPieceUtils::StartsWithIgnoreCase(base, "https://")) {
    184         base.remove_prefix(8);
    185       } else if (StringPieceUtils::StartsWithIgnoreCase(base, "http://")) {
    186         base.remove_prefix(7);
    187       }
    188     }
    189     int path_start = base.find_first_of('/');
    190     DCHECK_LT(0, path_start);
    191     StringPiece host(base.substr(0, path_start));
    192     StringPiece path(base.substr(path_start));
    193     if (path[path.length() - 1] == ',') {
    194       path.remove_suffix(1);
    195     }
    196     // Set up request headers. Assume method is GET and protocol is HTTP/1.1.
    197     request_headers.SetRequestFirstlineFromStringPieces("GET",
    198                                                         path,
    199                                                         "HTTP/1.1");
    200     request_headers.ReplaceOrAppendHeader("host", host);
    201 
    202     LOG(INFO) << "Inserting 'http://" << GetKey(request_headers)
    203               << "' into QuicInMemoryCache.";
    204 
    205     AddResponse(request_headers, response_headers, caching_visitor.body());
    206 
    207     file = file_list.Next();
    208   }
    209 }
    210 
    211 QuicInMemoryCache::~QuicInMemoryCache() {
    212   STLDeleteValues(&responses_);
    213 }
    214 
    215 string QuicInMemoryCache::GetKey(const BalsaHeaders& request_headers) const {
    216   StringPiece uri = request_headers.request_uri();
    217   StringPiece host;
    218   if (uri[0] == '/') {
    219     host = request_headers.GetHeader("host");
    220   } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) {
    221     uri.remove_prefix(8);
    222   } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) {
    223     uri.remove_prefix(7);
    224   }
    225   return host.as_string() + uri.as_string();
    226 }
    227 
    228 }  // namespace tools
    229 }  // namespace net
    230