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