| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/tools/quic/quic_in_memory_cache.h" | 5 #include "net/tools/quic/quic_in_memory_cache.h" |
| 6 | 6 |
| 7 #include "base/files/file_enumerator.h" | 7 #include "base/files/file_enumerator.h" |
| 8 #include "base/files/file_util.h" | 8 #include "base/files/file_util.h" |
| 9 #include "base/stl_util.h" | 9 #include "base/stl_util.h" |
| 10 #include "base/strings/string_number_conversions.h" | 10 #include "base/strings/string_number_conversions.h" |
| 11 #include "base/strings/string_util.h" | 11 #include "base/strings/string_util.h" |
| 12 #include "base/strings/stringprintf.h" | 12 #include "base/strings/stringprintf.h" |
| 13 #include "net/http/http_response_headers.h" | 13 #include "net/http/http_response_headers.h" |
| 14 #include "net/http/http_util.h" | 14 #include "net/http/http_util.h" |
| 15 #include "net/quic/quic_bug_tracker.h" | 15 #include "net/quic/quic_bug_tracker.h" |
| 16 #include "net/spdy/spdy_http_utils.h" | 16 #include "net/spdy/spdy_http_utils.h" |
| 17 | 17 |
| 18 using base::FilePath; | 18 using base::FilePath; |
| 19 using base::IntToString; | 19 using base::IntToString; |
| 20 using base::StringPiece; | 20 using base::StringPiece; |
| 21 using std::string; | 21 using std::string; |
| 22 | 22 |
| 23 namespace net { | 23 namespace net { |
| 24 | 24 |
| 25 namespace { |
| 26 |
| 27 class ResourceFileImpl : public net::QuicInMemoryCache::ResourceFile { |
| 28 public: |
| 29 ResourceFileImpl(const base::FilePath& file_name) : ResourceFile(file_name) {} |
| 30 |
| 31 void Read() override { |
| 32 base::ReadFileToString(FilePath(file_name_), &file_contents_); |
| 33 |
| 34 int file_len = static_cast<int>(file_contents_.length()); |
| 35 int headers_end = |
| 36 HttpUtil::LocateEndOfHeaders(file_contents_.data(), file_len); |
| 37 if (headers_end < 1) { |
| 38 LOG(DFATAL) << "Headers invalid or empty, ignoring: " |
| 39 << file_name_.value(); |
| 40 return; |
| 41 } |
| 42 http_headers_ = new HttpResponseHeaders( |
| 43 HttpUtil::AssembleRawHeaders(file_contents_.data(), headers_end)); |
| 44 |
| 45 if (http_headers_->GetNormalizedHeader("X-Original-Url", &url_)) { |
| 46 x_original_url_ = StringPiece(url_); |
| 47 HandleXOriginalUrl(); |
| 48 } |
| 49 |
| 50 // X-Push-URL header is a relatively quick way to support sever push |
| 51 // in the toy server. A production server should use link=preload |
| 52 // stuff as described in https://w3c.github.io/preload/. |
| 53 StringPiece x_push_url("X-Push-Url"); |
| 54 if (http_headers_->HasHeader(x_push_url)) { |
| 55 size_t iter = 0; |
| 56 std::unique_ptr<std::string> push_url(new string()); |
| 57 while ( |
| 58 http_headers_->EnumerateHeader(&iter, x_push_url, push_url.get())) { |
| 59 push_urls_.push_back(StringPiece(*push_url)); |
| 60 push_url_values_.push_back(std::move(push_url)); |
| 61 push_url.reset(new string()); |
| 62 } |
| 63 } |
| 64 |
| 65 body_ = StringPiece(file_contents_.data() + headers_end, |
| 66 file_contents_.size() - headers_end); |
| 67 |
| 68 CreateSpdyHeadersFromHttpResponse(*http_headers_, HTTP2, &spdy_headers_); |
| 69 } |
| 70 |
| 71 private: |
| 72 scoped_refptr<HttpResponseHeaders> http_headers_; |
| 73 std::string url_; |
| 74 std::list<std::unique_ptr<string>> push_url_values_; |
| 75 |
| 76 DISALLOW_COPY_AND_ASSIGN(ResourceFileImpl); |
| 77 }; |
| 78 |
| 79 } // namespace |
| 80 |
| 25 QuicInMemoryCache::ServerPushInfo::ServerPushInfo( | 81 QuicInMemoryCache::ServerPushInfo::ServerPushInfo( |
| 26 GURL request_url, | 82 GURL request_url, |
| 27 const SpdyHeaderBlock& headers, | 83 const SpdyHeaderBlock& headers, |
| 28 net::SpdyPriority priority, | 84 net::SpdyPriority priority, |
| 29 string body) | 85 string body) |
| 30 : request_url(request_url), | 86 : request_url(request_url), |
| 31 headers(headers), | 87 headers(headers), |
| 32 priority(priority), | 88 priority(priority), |
| 33 body(body) {} | 89 body(body) {} |
| 34 | 90 |
| 35 QuicInMemoryCache::ServerPushInfo::ServerPushInfo(const ServerPushInfo& other) = | 91 QuicInMemoryCache::ServerPushInfo::ServerPushInfo(const ServerPushInfo& other) = |
| 36 default; | 92 default; |
| 37 | 93 |
| 38 QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE) {} | 94 QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE) {} |
| 39 | 95 |
| 40 QuicInMemoryCache::Response::~Response() {} | 96 QuicInMemoryCache::Response::~Response() {} |
| 41 | 97 |
| 98 QuicInMemoryCache::ResourceFile::ResourceFile(const base::FilePath& file_name) |
| 99 : file_name_(file_name), file_name_string_(file_name.AsUTF8Unsafe()) {} |
| 100 |
| 101 QuicInMemoryCache::ResourceFile::~ResourceFile() {} |
| 102 |
| 103 void QuicInMemoryCache::ResourceFile::SetHostPathFromBase(StringPiece base) { |
| 104 size_t path_start = base.find_first_of('/'); |
| 105 DCHECK_LT(0UL, path_start); |
| 106 host_ = base.substr(0, path_start); |
| 107 size_t query_start = base.find_first_of(','); |
| 108 if (query_start > 0) { |
| 109 path_ = base.substr(path_start, query_start - 1); |
| 110 } else { |
| 111 path_ = base.substr(path_start); |
| 112 } |
| 113 } |
| 114 |
| 115 StringPiece QuicInMemoryCache::ResourceFile::RemoveScheme(StringPiece url) { |
| 116 if (url.starts_with("https://")) { |
| 117 url.remove_prefix(8); |
| 118 } else if (url.starts_with("http://")) { |
| 119 url.remove_prefix(7); |
| 120 } |
| 121 return url; |
| 122 } |
| 123 |
| 124 void QuicInMemoryCache::ResourceFile::HandleXOriginalUrl() { |
| 125 StringPiece url(x_original_url_); |
| 126 // Remove the protocol so we can add it below. |
| 127 url = RemoveScheme(url); |
| 128 SetHostPathFromBase(url); |
| 129 } |
| 130 |
| 42 // static | 131 // static |
| 43 QuicInMemoryCache* QuicInMemoryCache::GetInstance() { | 132 QuicInMemoryCache* QuicInMemoryCache::GetInstance() { |
| 44 return base::Singleton<QuicInMemoryCache>::get(); | 133 return base::Singleton<QuicInMemoryCache>::get(); |
| 45 } | 134 } |
| 46 | 135 |
| 47 const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse( | 136 const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse( |
| 48 StringPiece host, | 137 StringPiece host, |
| 49 StringPiece path) const { | 138 StringPiece path) const { |
| 50 ResponseMap::const_iterator it = responses_.find(GetKey(host, path)); | 139 ResponseMap::const_iterator it = responses_.find(GetKey(host, path)); |
| 51 if (it == responses_.end()) { | 140 if (it == responses_.end()) { |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 119 | 208 |
| 120 void QuicInMemoryCache::InitializeFromDirectory(const string& cache_directory) { | 209 void QuicInMemoryCache::InitializeFromDirectory(const string& cache_directory) { |
| 121 if (cache_directory.empty()) { | 210 if (cache_directory.empty()) { |
| 122 QUIC_BUG << "cache_directory must not be empty."; | 211 QUIC_BUG << "cache_directory must not be empty."; |
| 123 return; | 212 return; |
| 124 } | 213 } |
| 125 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: " | 214 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: " |
| 126 << cache_directory; | 215 << cache_directory; |
| 127 FilePath directory(FilePath::FromUTF8Unsafe(cache_directory)); | 216 FilePath directory(FilePath::FromUTF8Unsafe(cache_directory)); |
| 128 base::FileEnumerator file_list(directory, true, base::FileEnumerator::FILES); | 217 base::FileEnumerator file_list(directory, true, base::FileEnumerator::FILES); |
| 129 | 218 list<std::unique_ptr<ResourceFile>> resource_files; |
| 130 for (FilePath file_iter = file_list.Next(); !file_iter.empty(); | 219 for (FilePath file_iter = file_list.Next(); !file_iter.empty(); |
| 131 file_iter = file_list.Next()) { | 220 file_iter = file_list.Next()) { |
| 132 // Need to skip files in .svn directories | 221 // Need to skip files in .svn directories |
| 133 if (file_iter.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos) { | 222 if (file_iter.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos) { |
| 134 continue; | 223 continue; |
| 135 } | 224 } |
| 136 | 225 |
| 226 std::unique_ptr<ResourceFile> resource_file( |
| 227 new ResourceFileImpl(file_iter)); |
| 228 |
| 137 // Tease apart filename into host and path. | 229 // Tease apart filename into host and path. |
| 138 string file = file_iter.AsUTF8Unsafe(); | 230 StringPiece base(resource_file->file_name()); |
| 139 file.erase(0, cache_directory.length()); | 231 base.remove_prefix(cache_directory.length()); |
| 140 if (file[0] == '/') { | 232 if (base[0] == '/') { |
| 141 file.erase(0, 1); | 233 base.remove_prefix(1); |
| 142 } | 234 } |
| 143 | 235 |
| 144 string file_contents; | 236 resource_file->SetHostPathFromBase(base); |
| 145 base::ReadFileToString(file_iter, &file_contents); | 237 resource_file->Read(); |
| 146 int file_len = static_cast<int>(file_contents.length()); | 238 |
| 147 int headers_end = | 239 AddResponse(resource_file->host(), resource_file->path(), |
| 148 HttpUtil::LocateEndOfHeaders(file_contents.data(), file_len); | 240 resource_file->spdy_headers(), resource_file->body()); |
| 149 if (headers_end < 1) { | 241 |
| 150 LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file; | 242 resource_files.push_back(std::move(resource_file)); |
| 151 continue; | 243 } |
| 244 |
| 245 for (const auto& resource_file : resource_files) { |
| 246 list<ServerPushInfo> push_resources; |
| 247 for (const auto& push_url : resource_file->push_urls()) { |
| 248 GURL url(push_url); |
| 249 const Response* response = GetResponse(url.host(), url.path()); |
| 250 if (!response) { |
| 251 QUIC_BUG << "Push URL '" << push_url << "' not found."; |
| 252 return; |
| 253 } |
| 254 push_resources.push_back(ServerPushInfo(url, response->headers(), |
| 255 net::kV3LowestPriority, |
| 256 response->body().as_string())); |
| 152 } | 257 } |
| 153 | 258 MaybeAddServerPushResources(resource_file->host(), resource_file->path(), |
| 154 string raw_headers = | 259 push_resources); |
| 155 HttpUtil::AssembleRawHeaders(file_contents.data(), headers_end); | |
| 156 | |
| 157 scoped_refptr<HttpResponseHeaders> response_headers = | |
| 158 new HttpResponseHeaders(raw_headers); | |
| 159 | |
| 160 string base; | |
| 161 if (response_headers->GetNormalizedHeader("X-Original-Url", &base)) { | |
| 162 response_headers->RemoveHeader("X-Original-Url"); | |
| 163 // Remove the protocol so we can add it below. | |
| 164 if (base::StartsWith(base, "https://", | |
| 165 base::CompareCase::INSENSITIVE_ASCII)) { | |
| 166 base = base.substr(8); | |
| 167 } else if (base::StartsWith(base, "http://", | |
| 168 base::CompareCase::INSENSITIVE_ASCII)) { | |
| 169 base = base.substr(7); | |
| 170 } | |
| 171 } else { | |
| 172 base = file; | |
| 173 } | |
| 174 | |
| 175 size_t path_start = base.find_first_of('/'); | |
| 176 StringPiece host(StringPiece(base).substr(0, path_start)); | |
| 177 StringPiece path(StringPiece(base).substr(path_start)); | |
| 178 if (path.back() == ',') { | |
| 179 path.remove_suffix(1); | |
| 180 } | |
| 181 StringPiece body(file_contents.data() + headers_end, | |
| 182 file_contents.size() - headers_end); | |
| 183 SpdyHeaderBlock header_block; | |
| 184 CreateSpdyHeadersFromHttpResponse(*response_headers, HTTP2, &header_block); | |
| 185 AddResponse(host, path, header_block, body); | |
| 186 } | 260 } |
| 187 } | 261 } |
| 188 | 262 |
| 189 list<ServerPushInfo> QuicInMemoryCache::GetServerPushResources( | 263 list<ServerPushInfo> QuicInMemoryCache::GetServerPushResources( |
| 190 string request_url) { | 264 string request_url) { |
| 191 list<ServerPushInfo> resources; | 265 list<ServerPushInfo> resources; |
| 192 auto resource_range = server_push_resources_.equal_range(request_url); | 266 auto resource_range = server_push_resources_.equal_range(request_url); |
| 193 for (auto it = resource_range.first; it != resource_range.second; ++it) { | 267 for (auto it = resource_range.first; it != resource_range.second; ++it) { |
| 194 resources.push_back(it->second); | 268 resources.push_back(it->second); |
| 195 } | 269 } |
| 270 DVLOG(1) << "Found " << resources.size() << " push resources for " |
| 271 << request_url; |
| 196 return resources; | 272 return resources; |
| 197 } | 273 } |
| 198 | 274 |
| 199 QuicInMemoryCache::~QuicInMemoryCache() { | 275 QuicInMemoryCache::~QuicInMemoryCache() { |
| 200 STLDeleteValues(&responses_); | 276 STLDeleteValues(&responses_); |
| 201 } | 277 } |
| 202 | 278 |
| 203 void QuicInMemoryCache::AddResponseImpl( | 279 void QuicInMemoryCache::AddResponseImpl( |
| 204 StringPiece host, | 280 StringPiece host, |
| 205 StringPiece path, | 281 StringPiece path, |
| 206 SpecialResponseType response_type, | 282 SpecialResponseType response_type, |
| 207 const SpdyHeaderBlock& response_headers, | 283 const SpdyHeaderBlock& response_headers, |
| 208 StringPiece response_body, | 284 StringPiece response_body, |
| 209 const SpdyHeaderBlock& response_trailers) { | 285 const SpdyHeaderBlock& response_trailers) { |
| 210 DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\""; | 286 DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\""; |
| 211 string key = GetKey(host, path); | 287 string key = GetKey(host, path); |
| 212 if (ContainsKey(responses_, key)) { | 288 if (ContainsKey(responses_, key)) { |
| 213 QUIC_BUG << "Response for '" << key << "' already exists!"; | 289 QUIC_BUG << "Response for '" << key << "' already exists!"; |
| 214 return; | 290 return; |
| 215 } | 291 } |
| 216 Response* new_response = new Response(); | 292 Response* new_response = new Response(); |
| 217 new_response->set_response_type(response_type); | 293 new_response->set_response_type(response_type); |
| 218 new_response->set_headers(response_headers); | 294 new_response->set_headers(response_headers); |
| 219 new_response->set_body(response_body); | 295 new_response->set_body(response_body); |
| 220 new_response->set_trailers(response_trailers); | 296 new_response->set_trailers(response_trailers); |
| 297 DVLOG(1) << "Add response with key " << key; |
| 221 responses_[key] = new_response; | 298 responses_[key] = new_response; |
| 222 } | 299 } |
| 223 | 300 |
| 224 string QuicInMemoryCache::GetKey(StringPiece host, StringPiece path) const { | 301 string QuicInMemoryCache::GetKey(StringPiece host, StringPiece path) const { |
| 225 return host.as_string() + path.as_string(); | 302 return host.as_string() + path.as_string(); |
| 226 } | 303 } |
| 227 | 304 |
| 228 void QuicInMemoryCache::MaybeAddServerPushResources( | 305 void QuicInMemoryCache::MaybeAddServerPushResources( |
| 229 StringPiece request_host, | 306 StringPiece request_host, |
| 230 StringPiece request_path, | 307 StringPiece request_path, |
| 231 list<ServerPushInfo> push_resources) { | 308 list<ServerPushInfo> push_resources) { |
| 232 string request_url = request_host.as_string() + request_path.as_string(); | 309 string request_url = GetKey(request_host, request_path); |
| 233 | 310 |
| 234 for (const auto& push_resource : push_resources) { | 311 for (const auto& push_resource : push_resources) { |
| 235 if (PushResourceExistsInCache(request_url, push_resource)) { | 312 if (PushResourceExistsInCache(request_url, push_resource)) { |
| 236 continue; | 313 continue; |
| 237 } | 314 } |
| 238 | 315 |
| 239 DVLOG(1) << "Add request-resource association."; | 316 DVLOG(1) << "Add request-resource association: request url " << request_url |
| 317 << " push url " << push_resource.request_url |
| 318 << " response headers " << push_resource.headers.DebugString(); |
| 240 server_push_resources_.insert(std::make_pair(request_url, push_resource)); | 319 server_push_resources_.insert(std::make_pair(request_url, push_resource)); |
| 241 string host = push_resource.request_url.host(); | 320 string host = push_resource.request_url.host(); |
| 242 if (host.empty()) { | 321 if (host.empty()) { |
| 243 host = request_host.as_string(); | 322 host = request_host.as_string(); |
| 244 } | 323 } |
| 245 string path = push_resource.request_url.path(); | 324 string path = push_resource.request_url.path(); |
| 246 if (responses_.find(GetKey(host, path)) == responses_.end()) { | 325 if (responses_.find(GetKey(host, path)) == responses_.end()) { |
| 247 // Add a server push response to responses map, if it is not in the map. | 326 // Add a server push response to responses map, if it is not in the map. |
| 248 SpdyHeaderBlock headers = push_resource.headers; | 327 SpdyHeaderBlock headers = push_resource.headers; |
| 249 StringPiece body = push_resource.body; | 328 StringPiece body = push_resource.body; |
| 250 DVLOG(1) << "Add response for push resource: host " << host << " path " | 329 DVLOG(1) << "Add response for push resource: host " << host << " path " |
| 251 << path << " body " << body; | 330 << path; |
| 252 AddResponse(host, path, headers, body); | 331 AddResponse(host, path, headers, body); |
| 253 } | 332 } |
| 254 } | 333 } |
| 255 } | 334 } |
| 256 | 335 |
| 257 bool QuicInMemoryCache::PushResourceExistsInCache(string original_request_url, | 336 bool QuicInMemoryCache::PushResourceExistsInCache(string original_request_url, |
| 258 ServerPushInfo resource) { | 337 ServerPushInfo resource) { |
| 259 auto resource_range = | 338 auto resource_range = |
| 260 server_push_resources_.equal_range(original_request_url); | 339 server_push_resources_.equal_range(original_request_url); |
| 261 for (auto it = resource_range.first; it != resource_range.second; ++it) { | 340 for (auto it = resource_range.first; it != resource_range.second; ++it) { |
| 262 ServerPushInfo push_resource = it->second; | 341 ServerPushInfo push_resource = it->second; |
| 263 if (push_resource.request_url.spec() == resource.request_url.spec()) { | 342 if (push_resource.request_url.spec() == resource.request_url.spec()) { |
| 264 return true; | 343 return true; |
| 265 } | 344 } |
| 266 } | 345 } |
| 267 return false; | 346 return false; |
| 268 } | 347 } |
| 269 | 348 |
| 270 } // namespace net | 349 } // namespace net |
| OLD | NEW |