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