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 |