| OLD | NEW |
| (Empty) |
| 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/files/file_enumerator.h" | |
| 8 #include "base/files/file_util.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 void ProcessBodyData(const char* input, size_t size) override { | |
| 33 AppendToBody(input, size); | |
| 34 } | |
| 35 void MessageDone() override { done_framing_ = true; } | |
| 36 void HandleHeaderError(BalsaFrame* framer) override { UnhandledError(); } | |
| 37 void HandleHeaderWarning(BalsaFrame* framer) override { UnhandledError(); } | |
| 38 void HandleChunkingError(BalsaFrame* framer) override { UnhandledError(); } | |
| 39 void HandleBodyError(BalsaFrame* framer) override { UnhandledError(); } | |
| 40 void UnhandledError() { | |
| 41 LOG(DFATAL) << "Unhandled error framing HTTP."; | |
| 42 } | |
| 43 void AppendToBody(const char* input, size_t size) { | |
| 44 body_.append(input, size); | |
| 45 } | |
| 46 bool done_framing() const { return done_framing_; } | |
| 47 const string& body() const { return body_; } | |
| 48 | |
| 49 private: | |
| 50 bool done_framing_; | |
| 51 string body_; | |
| 52 }; | |
| 53 | |
| 54 } // namespace | |
| 55 | |
| 56 // static | |
| 57 QuicInMemoryCache* QuicInMemoryCache::GetInstance() { | |
| 58 return Singleton<QuicInMemoryCache>::get(); | |
| 59 } | |
| 60 | |
| 61 const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse( | |
| 62 const BalsaHeaders& request_headers) const { | |
| 63 ResponseMap::const_iterator it = responses_.find(GetKey(request_headers)); | |
| 64 if (it == responses_.end()) { | |
| 65 return nullptr; | |
| 66 } | |
| 67 return it->second; | |
| 68 } | |
| 69 | |
| 70 void QuicInMemoryCache::AddSimpleResponse(StringPiece method, | |
| 71 StringPiece path, | |
| 72 StringPiece version, | |
| 73 StringPiece response_code, | |
| 74 StringPiece response_detail, | |
| 75 StringPiece body) { | |
| 76 BalsaHeaders request_headers, response_headers; | |
| 77 request_headers.SetRequestFirstlineFromStringPieces(method, | |
| 78 path, | |
| 79 version); | |
| 80 response_headers.SetRequestFirstlineFromStringPieces(version, | |
| 81 response_code, | |
| 82 response_detail); | |
| 83 response_headers.AppendHeader("content-length", | |
| 84 base::IntToString(body.length())); | |
| 85 | |
| 86 AddResponse(request_headers, response_headers, body); | |
| 87 } | |
| 88 | |
| 89 void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers, | |
| 90 const BalsaHeaders& response_headers, | |
| 91 StringPiece response_body) { | |
| 92 VLOG(1) << "Adding response for: " << GetKey(request_headers); | |
| 93 if (ContainsKey(responses_, GetKey(request_headers))) { | |
| 94 LOG(DFATAL) << "Response for given request already exists!"; | |
| 95 return; | |
| 96 } | |
| 97 Response* new_response = new Response(); | |
| 98 new_response->set_headers(response_headers); | |
| 99 new_response->set_body(response_body); | |
| 100 responses_[GetKey(request_headers)] = new_response; | |
| 101 } | |
| 102 | |
| 103 void QuicInMemoryCache::AddSpecialResponse(StringPiece method, | |
| 104 StringPiece path, | |
| 105 StringPiece version, | |
| 106 SpecialResponseType response_type) { | |
| 107 BalsaHeaders request_headers, response_headers; | |
| 108 request_headers.SetRequestFirstlineFromStringPieces(method, | |
| 109 path, | |
| 110 version); | |
| 111 AddResponse(request_headers, response_headers, ""); | |
| 112 responses_[GetKey(request_headers)]->response_type_ = response_type; | |
| 113 } | |
| 114 | |
| 115 QuicInMemoryCache::QuicInMemoryCache() { | |
| 116 Initialize(); | |
| 117 } | |
| 118 | |
| 119 void QuicInMemoryCache::ResetForTests() { | |
| 120 STLDeleteValues(&responses_); | |
| 121 Initialize(); | |
| 122 } | |
| 123 | |
| 124 void QuicInMemoryCache::Initialize() { | |
| 125 // If there's no defined cache dir, we have no initialization to do. | |
| 126 if (FLAGS_quic_in_memory_cache_dir.empty()) { | |
| 127 VLOG(1) << "No cache directory found. Skipping initialization."; | |
| 128 return; | |
| 129 } | |
| 130 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: " | |
| 131 << FLAGS_quic_in_memory_cache_dir; | |
| 132 | |
| 133 FilePath directory(FLAGS_quic_in_memory_cache_dir); | |
| 134 base::FileEnumerator file_list(directory, | |
| 135 true, | |
| 136 base::FileEnumerator::FILES); | |
| 137 | |
| 138 FilePath file = file_list.Next(); | |
| 139 while (!file.empty()) { | |
| 140 // Need to skip files in .svn directories | |
| 141 if (file.value().find("/.svn/") != std::string::npos) { | |
| 142 file = file_list.Next(); | |
| 143 continue; | |
| 144 } | |
| 145 | |
| 146 BalsaHeaders request_headers, response_headers; | |
| 147 | |
| 148 string file_contents; | |
| 149 base::ReadFileToString(file, &file_contents); | |
| 150 | |
| 151 // Frame HTTP. | |
| 152 CachingBalsaVisitor caching_visitor; | |
| 153 BalsaFrame framer; | |
| 154 framer.set_balsa_headers(&response_headers); | |
| 155 framer.set_balsa_visitor(&caching_visitor); | |
| 156 size_t processed = 0; | |
| 157 while (processed < file_contents.length() && | |
| 158 !caching_visitor.done_framing()) { | |
| 159 processed += framer.ProcessInput(file_contents.c_str() + processed, | |
| 160 file_contents.length() - processed); | |
| 161 } | |
| 162 | |
| 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 VLOG(1) << "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 if (uri.size() == 0) { | |
| 218 return ""; | |
| 219 } | |
| 220 StringPiece host; | |
| 221 if (uri[0] == '/') { | |
| 222 host = request_headers.GetHeader("host"); | |
| 223 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) { | |
| 224 uri.remove_prefix(8); | |
| 225 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) { | |
| 226 uri.remove_prefix(7); | |
| 227 } | |
| 228 return host.as_string() + uri.as_string(); | |
| 229 } | |
| 230 | |
| 231 } // namespace tools | |
| 232 } // namespace net | |
| OLD | NEW |