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