| 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 <utility> | |
| 8 | |
| 9 #include "base/files/file_enumerator.h" | |
| 10 #include "base/files/file_util.h" | |
| 11 #include "base/memory/ptr_util.h" | |
| 12 #include "base/stl_util.h" | |
| 13 #include "base/strings/string_number_conversions.h" | |
| 14 #include "base/strings/string_util.h" | |
| 15 #include "base/strings/stringprintf.h" | |
| 16 #include "net/http/http_util.h" | |
| 17 #include "net/quic/core/quic_bug_tracker.h" | |
| 18 #include "net/spdy/spdy_http_utils.h" | |
| 19 | |
| 20 using base::FilePath; | |
| 21 using base::IntToString; | |
| 22 using base::StringPiece; | |
| 23 using std::string; | |
| 24 | |
| 25 namespace net { | |
| 26 | |
| 27 QuicInMemoryCache::ServerPushInfo::ServerPushInfo(GURL request_url, | |
| 28 SpdyHeaderBlock headers, | |
| 29 net::SpdyPriority priority, | |
| 30 string body) | |
| 31 : request_url(request_url), | |
| 32 headers(std::move(headers)), | |
| 33 priority(priority), | |
| 34 body(body) {} | |
| 35 | |
| 36 QuicInMemoryCache::ServerPushInfo::ServerPushInfo(const ServerPushInfo& other) | |
| 37 : request_url(other.request_url), | |
| 38 headers(other.headers.Clone()), | |
| 39 priority(other.priority), | |
| 40 body(other.body) {} | |
| 41 | |
| 42 QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE) {} | |
| 43 | |
| 44 QuicInMemoryCache::Response::~Response() {} | |
| 45 | |
| 46 void QuicInMemoryCache::ResourceFile::Read() { | |
| 47 base::ReadFileToString(FilePath(file_name_), &file_contents_); | |
| 48 | |
| 49 // First read the headers. | |
| 50 size_t start = 0; | |
| 51 while (start < file_contents_.length()) { | |
| 52 size_t pos = file_contents_.find("\n", start); | |
| 53 if (pos == string::npos) { | |
| 54 LOG(DFATAL) << "Headers invalid or empty, ignoring: " | |
| 55 << file_name_.value(); | |
| 56 return; | |
| 57 } | |
| 58 size_t len = pos - start; | |
| 59 // Support both dos and unix line endings for convenience. | |
| 60 if (file_contents_[pos - 1] == '\r') { | |
| 61 len -= 1; | |
| 62 } | |
| 63 StringPiece line(file_contents_.data() + start, len); | |
| 64 start = pos + 1; | |
| 65 // Headers end with an empty line. | |
| 66 if (line.empty()) { | |
| 67 break; | |
| 68 } | |
| 69 // Extract the status from the HTTP first line. | |
| 70 if (line.substr(0, 4) == "HTTP") { | |
| 71 pos = line.find(" "); | |
| 72 if (pos == string::npos) { | |
| 73 LOG(DFATAL) << "Headers invalid or empty, ignoring: " | |
| 74 << file_name_.value(); | |
| 75 return; | |
| 76 } | |
| 77 spdy_headers_[":status"] = line.substr(pos + 1, 3); | |
| 78 continue; | |
| 79 } | |
| 80 // Headers are "key: value". | |
| 81 pos = line.find(": "); | |
| 82 if (pos == string::npos) { | |
| 83 LOG(DFATAL) << "Headers invalid or empty, ignoring: " | |
| 84 << file_name_.value(); | |
| 85 return; | |
| 86 } | |
| 87 spdy_headers_.AppendValueOrAddHeader( | |
| 88 base::ToLowerASCII(line.substr(0, pos)), line.substr(pos + 2)); | |
| 89 } | |
| 90 | |
| 91 // The connection header is prohibited in HTTP/2. | |
| 92 spdy_headers_.erase("connection"); | |
| 93 | |
| 94 // Override the URL with the X-Original-Url header, if present. | |
| 95 auto it = spdy_headers_.find("x-original-url"); | |
| 96 if (it != spdy_headers_.end()) { | |
| 97 x_original_url_ = it->second; | |
| 98 HandleXOriginalUrl(); | |
| 99 } | |
| 100 | |
| 101 // X-Push-URL header is a relatively quick way to support sever push | |
| 102 // in the toy server. A production server should use link=preload | |
| 103 // stuff as described in https://w3c.github.io/preload/. | |
| 104 it = spdy_headers_.find("x-push-url"); | |
| 105 if (it != spdy_headers_.end()) { | |
| 106 StringPiece push_urls = it->second; | |
| 107 size_t start = 0; | |
| 108 while (start < push_urls.length()) { | |
| 109 size_t pos = push_urls.find('\0', start); | |
| 110 if (pos == string::npos) { | |
| 111 push_urls_.push_back( | |
| 112 StringPiece(push_urls.data() + start, push_urls.length() - start)); | |
| 113 break; | |
| 114 } | |
| 115 push_urls_.push_back(StringPiece(push_urls.data() + start, pos)); | |
| 116 start += pos + 1; | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 body_ = | |
| 121 StringPiece(file_contents_.data() + start, file_contents_.size() - start); | |
| 122 } | |
| 123 | |
| 124 QuicInMemoryCache::ResourceFile::ResourceFile(const base::FilePath& file_name) | |
| 125 : file_name_(file_name), file_name_string_(file_name.AsUTF8Unsafe()) {} | |
| 126 | |
| 127 QuicInMemoryCache::ResourceFile::~ResourceFile() {} | |
| 128 | |
| 129 void QuicInMemoryCache::ResourceFile::SetHostPathFromBase(StringPiece base) { | |
| 130 size_t path_start = base.find_first_of('/'); | |
| 131 DCHECK_LT(0UL, path_start); | |
| 132 host_ = base.substr(0, path_start); | |
| 133 size_t query_start = base.find_first_of(','); | |
| 134 if (query_start > 0) { | |
| 135 path_ = base.substr(path_start, query_start - 1); | |
| 136 } else { | |
| 137 path_ = base.substr(path_start); | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 StringPiece QuicInMemoryCache::ResourceFile::RemoveScheme(StringPiece url) { | |
| 142 if (base::StartsWith(url, "https://", base::CompareCase::INSENSITIVE_ASCII)) { | |
| 143 url.remove_prefix(8); | |
| 144 } else if (base::StartsWith(url, "http://", | |
| 145 base::CompareCase::INSENSITIVE_ASCII)) { | |
| 146 url.remove_prefix(7); | |
| 147 } | |
| 148 return url; | |
| 149 } | |
| 150 | |
| 151 void QuicInMemoryCache::ResourceFile::HandleXOriginalUrl() { | |
| 152 StringPiece url(x_original_url_); | |
| 153 // Remove the protocol so we can add it below. | |
| 154 url = RemoveScheme(url); | |
| 155 SetHostPathFromBase(url); | |
| 156 } | |
| 157 | |
| 158 const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse( | |
| 159 StringPiece host, | |
| 160 StringPiece path) const { | |
| 161 base::AutoLock lock(response_mutex_); | |
| 162 | |
| 163 auto it = responses_.find(GetKey(host, path)); | |
| 164 if (it == responses_.end()) { | |
| 165 DVLOG(1) << "Get response for resource failed: host " << host << " path " | |
| 166 << path; | |
| 167 if (default_response_.get()) { | |
| 168 return default_response_.get(); | |
| 169 } | |
| 170 return nullptr; | |
| 171 } | |
| 172 return it->second.get(); | |
| 173 } | |
| 174 | |
| 175 typedef QuicInMemoryCache::ServerPushInfo ServerPushInfo; | |
| 176 | |
| 177 void QuicInMemoryCache::AddSimpleResponse(StringPiece host, | |
| 178 StringPiece path, | |
| 179 int response_code, | |
| 180 StringPiece body) { | |
| 181 SpdyHeaderBlock response_headers; | |
| 182 response_headers[":status"] = IntToString(response_code); | |
| 183 response_headers["content-length"] = | |
| 184 IntToString(static_cast<int>(body.length())); | |
| 185 AddResponse(host, path, std::move(response_headers), body); | |
| 186 } | |
| 187 | |
| 188 void QuicInMemoryCache::AddSimpleResponseWithServerPushResources( | |
| 189 StringPiece host, | |
| 190 StringPiece path, | |
| 191 int response_code, | |
| 192 StringPiece body, | |
| 193 std::list<ServerPushInfo> push_resources) { | |
| 194 AddSimpleResponse(host, path, response_code, body); | |
| 195 MaybeAddServerPushResources(host, path, push_resources); | |
| 196 } | |
| 197 | |
| 198 void QuicInMemoryCache::AddDefaultResponse(Response* response) { | |
| 199 base::AutoLock lock(response_mutex_); | |
| 200 default_response_.reset(response); | |
| 201 } | |
| 202 | |
| 203 void QuicInMemoryCache::AddResponse(StringPiece host, | |
| 204 StringPiece path, | |
| 205 SpdyHeaderBlock response_headers, | |
| 206 StringPiece response_body) { | |
| 207 AddResponseImpl(host, path, REGULAR_RESPONSE, std::move(response_headers), | |
| 208 response_body, SpdyHeaderBlock()); | |
| 209 } | |
| 210 | |
| 211 void QuicInMemoryCache::AddResponse(StringPiece host, | |
| 212 StringPiece path, | |
| 213 SpdyHeaderBlock response_headers, | |
| 214 StringPiece response_body, | |
| 215 SpdyHeaderBlock response_trailers) { | |
| 216 AddResponseImpl(host, path, REGULAR_RESPONSE, std::move(response_headers), | |
| 217 response_body, std::move(response_trailers)); | |
| 218 } | |
| 219 | |
| 220 void QuicInMemoryCache::AddSpecialResponse(StringPiece host, | |
| 221 StringPiece path, | |
| 222 SpecialResponseType response_type) { | |
| 223 AddResponseImpl(host, path, response_type, SpdyHeaderBlock(), "", | |
| 224 SpdyHeaderBlock()); | |
| 225 } | |
| 226 | |
| 227 QuicInMemoryCache::QuicInMemoryCache() {} | |
| 228 | |
| 229 void QuicInMemoryCache::InitializeFromDirectory(const string& cache_directory) { | |
| 230 if (cache_directory.empty()) { | |
| 231 QUIC_BUG << "cache_directory must not be empty."; | |
| 232 return; | |
| 233 } | |
| 234 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: " | |
| 235 << cache_directory; | |
| 236 FilePath directory(FilePath::FromUTF8Unsafe(cache_directory)); | |
| 237 base::FileEnumerator file_list(directory, true, base::FileEnumerator::FILES); | |
| 238 std::list<std::unique_ptr<ResourceFile>> resource_files; | |
| 239 for (FilePath file_iter = file_list.Next(); !file_iter.empty(); | |
| 240 file_iter = file_list.Next()) { | |
| 241 // Need to skip files in .svn directories | |
| 242 if (file_iter.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos) { | |
| 243 continue; | |
| 244 } | |
| 245 | |
| 246 std::unique_ptr<ResourceFile> resource_file(new ResourceFile(file_iter)); | |
| 247 | |
| 248 // Tease apart filename into host and path. | |
| 249 StringPiece base(resource_file->file_name()); | |
| 250 base.remove_prefix(cache_directory.length()); | |
| 251 if (base[0] == '/') { | |
| 252 base.remove_prefix(1); | |
| 253 } | |
| 254 | |
| 255 resource_file->SetHostPathFromBase(base); | |
| 256 resource_file->Read(); | |
| 257 | |
| 258 AddResponse(resource_file->host(), resource_file->path(), | |
| 259 resource_file->spdy_headers().Clone(), resource_file->body()); | |
| 260 | |
| 261 resource_files.push_back(std::move(resource_file)); | |
| 262 } | |
| 263 | |
| 264 for (const auto& resource_file : resource_files) { | |
| 265 std::list<ServerPushInfo> push_resources; | |
| 266 for (const auto& push_url : resource_file->push_urls()) { | |
| 267 GURL url(push_url); | |
| 268 const Response* response = GetResponse(url.host(), url.path()); | |
| 269 if (!response) { | |
| 270 QUIC_BUG << "Push URL '" << push_url << "' not found."; | |
| 271 return; | |
| 272 } | |
| 273 push_resources.push_back(ServerPushInfo(url, response->headers().Clone(), | |
| 274 net::kV3LowestPriority, | |
| 275 response->body().as_string())); | |
| 276 } | |
| 277 MaybeAddServerPushResources(resource_file->host(), resource_file->path(), | |
| 278 push_resources); | |
| 279 } | |
| 280 } | |
| 281 | |
| 282 std::list<ServerPushInfo> QuicInMemoryCache::GetServerPushResources( | |
| 283 string request_url) { | |
| 284 base::AutoLock lock(response_mutex_); | |
| 285 | |
| 286 std::list<ServerPushInfo> resources; | |
| 287 auto resource_range = server_push_resources_.equal_range(request_url); | |
| 288 for (auto it = resource_range.first; it != resource_range.second; ++it) { | |
| 289 resources.push_back(it->second); | |
| 290 } | |
| 291 DVLOG(1) << "Found " << resources.size() << " push resources for " | |
| 292 << request_url; | |
| 293 return resources; | |
| 294 } | |
| 295 | |
| 296 QuicInMemoryCache::~QuicInMemoryCache() { | |
| 297 { | |
| 298 base::AutoLock lock(response_mutex_); | |
| 299 responses_.clear(); | |
| 300 } | |
| 301 } | |
| 302 | |
| 303 void QuicInMemoryCache::AddResponseImpl(StringPiece host, | |
| 304 StringPiece path, | |
| 305 SpecialResponseType response_type, | |
| 306 SpdyHeaderBlock response_headers, | |
| 307 StringPiece response_body, | |
| 308 SpdyHeaderBlock response_trailers) { | |
| 309 base::AutoLock lock(response_mutex_); | |
| 310 | |
| 311 DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\""; | |
| 312 string key = GetKey(host, path); | |
| 313 if (base::ContainsKey(responses_, key)) { | |
| 314 QUIC_BUG << "Response for '" << key << "' already exists!"; | |
| 315 return; | |
| 316 } | |
| 317 std::unique_ptr<Response> new_response = base::MakeUnique<Response>(); | |
| 318 new_response->set_response_type(response_type); | |
| 319 new_response->set_headers(std::move(response_headers)); | |
| 320 new_response->set_body(response_body); | |
| 321 new_response->set_trailers(std::move(response_trailers)); | |
| 322 DVLOG(1) << "Add response with key " << key; | |
| 323 responses_[key] = std::move(new_response); | |
| 324 } | |
| 325 | |
| 326 string QuicInMemoryCache::GetKey(StringPiece host, StringPiece path) const { | |
| 327 return host.as_string() + path.as_string(); | |
| 328 } | |
| 329 | |
| 330 void QuicInMemoryCache::MaybeAddServerPushResources( | |
| 331 StringPiece request_host, | |
| 332 StringPiece request_path, | |
| 333 std::list<ServerPushInfo> push_resources) { | |
| 334 string request_url = GetKey(request_host, request_path); | |
| 335 | |
| 336 for (const auto& push_resource : push_resources) { | |
| 337 if (PushResourceExistsInCache(request_url, push_resource)) { | |
| 338 continue; | |
| 339 } | |
| 340 | |
| 341 DVLOG(1) << "Add request-resource association: request url " << request_url | |
| 342 << " push url " << push_resource.request_url | |
| 343 << " response headers " << push_resource.headers.DebugString(); | |
| 344 { | |
| 345 base::AutoLock lock(response_mutex_); | |
| 346 server_push_resources_.insert(std::make_pair(request_url, push_resource)); | |
| 347 } | |
| 348 string host = push_resource.request_url.host(); | |
| 349 if (host.empty()) { | |
| 350 host = request_host.as_string(); | |
| 351 } | |
| 352 string path = push_resource.request_url.path(); | |
| 353 bool found_existing_response = false; | |
| 354 { | |
| 355 base::AutoLock lock(response_mutex_); | |
| 356 found_existing_response = | |
| 357 base::ContainsKey(responses_, GetKey(host, path)); | |
| 358 } | |
| 359 if (!found_existing_response) { | |
| 360 // Add a server push response to responses map, if it is not in the map. | |
| 361 StringPiece body = push_resource.body; | |
| 362 DVLOG(1) << "Add response for push resource: host " << host << " path " | |
| 363 << path; | |
| 364 AddResponse(host, path, push_resource.headers.Clone(), body); | |
| 365 } | |
| 366 } | |
| 367 } | |
| 368 | |
| 369 bool QuicInMemoryCache::PushResourceExistsInCache(string original_request_url, | |
| 370 ServerPushInfo resource) { | |
| 371 base::AutoLock lock(response_mutex_); | |
| 372 auto resource_range = | |
| 373 server_push_resources_.equal_range(original_request_url); | |
| 374 for (auto it = resource_range.first; it != resource_range.second; ++it) { | |
| 375 ServerPushInfo push_resource = it->second; | |
| 376 if (push_resource.request_url.spec() == resource.request_url.spec()) { | |
| 377 return true; | |
| 378 } | |
| 379 } | |
| 380 return false; | |
| 381 } | |
| 382 | |
| 383 } // namespace net | |
| OLD | NEW |