Project import generated by Copybara.
PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/tools/quic_memory_cache_backend.cc b/quic/tools/quic_memory_cache_backend.cc
new file mode 100644
index 0000000..9f333dd
--- /dev/null
+++ b/quic/tools/quic_memory_cache_backend.cc
@@ -0,0 +1,419 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h"
+
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_file_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using spdy::kV3LowestPriority;
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicMemoryCacheBackend::ResourceFile::ResourceFile(const QuicString& file_name)
+ : file_name_(file_name) {}
+
+QuicMemoryCacheBackend::ResourceFile::~ResourceFile() = default;
+
+void QuicMemoryCacheBackend::ResourceFile::Read() {
+ ReadFileContents(file_name_, &file_contents_);
+
+ // First read the headers.
+ size_t start = 0;
+ while (start < file_contents_.length()) {
+ size_t pos = file_contents_.find("\n", start);
+ if (pos == QuicString::npos) {
+ QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_;
+ return;
+ }
+ size_t len = pos - start;
+ // Support both dos and unix line endings for convenience.
+ if (file_contents_[pos - 1] == '\r') {
+ len -= 1;
+ }
+ QuicStringPiece line(file_contents_.data() + start, len);
+ start = pos + 1;
+ // Headers end with an empty line.
+ if (line.empty()) {
+ break;
+ }
+ // Extract the status from the HTTP first line.
+ if (line.substr(0, 4) == "HTTP") {
+ pos = line.find(" ");
+ if (pos == QuicString::npos) {
+ QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: "
+ << file_name_;
+ return;
+ }
+ spdy_headers_[":status"] = line.substr(pos + 1, 3);
+ continue;
+ }
+ // Headers are "key: value".
+ pos = line.find(": ");
+ if (pos == QuicString::npos) {
+ QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_;
+ return;
+ }
+ spdy_headers_.AppendValueOrAddHeader(
+ QuicTextUtils::ToLower(line.substr(0, pos)), line.substr(pos + 2));
+ }
+
+ // The connection header is prohibited in HTTP/2.
+ spdy_headers_.erase("connection");
+
+ // Override the URL with the X-Original-Url header, if present.
+ auto it = spdy_headers_.find("x-original-url");
+ if (it != spdy_headers_.end()) {
+ x_original_url_ = it->second;
+ HandleXOriginalUrl();
+ }
+
+ // X-Push-URL header is a relatively quick way to support sever push
+ // in the toy server. A production server should use link=preload
+ // stuff as described in https://w3c.github.io/preload/.
+ it = spdy_headers_.find("x-push-url");
+ if (it != spdy_headers_.end()) {
+ QuicStringPiece push_urls = it->second;
+ size_t start = 0;
+ while (start < push_urls.length()) {
+ size_t pos = push_urls.find('\0', start);
+ if (pos == QuicString::npos) {
+ push_urls_.push_back(QuicStringPiece(push_urls.data() + start,
+ push_urls.length() - start));
+ break;
+ }
+ push_urls_.push_back(QuicStringPiece(push_urls.data() + start, pos));
+ start += pos + 1;
+ }
+ }
+
+ body_ = QuicStringPiece(file_contents_.data() + start,
+ file_contents_.size() - start);
+}
+
+void QuicMemoryCacheBackend::ResourceFile::SetHostPathFromBase(
+ QuicStringPiece base) {
+ size_t path_start = base.find_first_of('/');
+ DCHECK_LT(0UL, path_start);
+ host_ = base.substr(0, path_start);
+ size_t query_start = base.find_first_of(',');
+ if (query_start > 0) {
+ path_ = base.substr(path_start, query_start - 1);
+ } else {
+ path_ = base.substr(path_start);
+ }
+}
+
+QuicStringPiece QuicMemoryCacheBackend::ResourceFile::RemoveScheme(
+ QuicStringPiece url) {
+ if (QuicTextUtils::StartsWith(url, "https://")) {
+ url.remove_prefix(8);
+ } else if (QuicTextUtils::StartsWith(url, "http://")) {
+ url.remove_prefix(7);
+ }
+ return url;
+}
+
+void QuicMemoryCacheBackend::ResourceFile::HandleXOriginalUrl() {
+ QuicStringPiece url(x_original_url_);
+ // Remove the protocol so we can add it below.
+ url = RemoveScheme(url);
+ SetHostPathFromBase(url);
+}
+
+const QuicBackendResponse* QuicMemoryCacheBackend::GetResponse(
+ QuicStringPiece host,
+ QuicStringPiece path) const {
+ QuicWriterMutexLock lock(&response_mutex_);
+
+ auto it = responses_.find(GetKey(host, path));
+ if (it == responses_.end()) {
+ DVLOG(1) << "Get response for resource failed: host " << host << " path "
+ << path;
+ if (default_response_) {
+ return default_response_.get();
+ }
+ return nullptr;
+ }
+ return it->second.get();
+}
+
+typedef QuicBackendResponse::ServerPushInfo ServerPushInfo;
+typedef QuicBackendResponse::SpecialResponseType SpecialResponseType;
+
+void QuicMemoryCacheBackend::AddSimpleResponse(QuicStringPiece host,
+ QuicStringPiece path,
+ int response_code,
+ QuicStringPiece body) {
+ SpdyHeaderBlock response_headers;
+ response_headers[":status"] = QuicTextUtils::Uint64ToString(response_code);
+ response_headers["content-length"] =
+ QuicTextUtils::Uint64ToString(body.length());
+ AddResponse(host, path, std::move(response_headers), body);
+}
+
+void QuicMemoryCacheBackend::AddSimpleResponseWithServerPushResources(
+ QuicStringPiece host,
+ QuicStringPiece path,
+ int response_code,
+ QuicStringPiece body,
+ std::list<ServerPushInfo> push_resources) {
+ AddSimpleResponse(host, path, response_code, body);
+ MaybeAddServerPushResources(host, path, push_resources);
+}
+
+void QuicMemoryCacheBackend::AddDefaultResponse(QuicBackendResponse* response) {
+ QuicWriterMutexLock lock(&response_mutex_);
+ default_response_.reset(response);
+}
+
+void QuicMemoryCacheBackend::AddResponse(QuicStringPiece host,
+ QuicStringPiece path,
+ SpdyHeaderBlock response_headers,
+ QuicStringPiece response_body) {
+ AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
+ std::move(response_headers), response_body, SpdyHeaderBlock(),
+ 0);
+}
+
+void QuicMemoryCacheBackend::AddResponse(QuicStringPiece host,
+ QuicStringPiece path,
+ SpdyHeaderBlock response_headers,
+ QuicStringPiece response_body,
+ SpdyHeaderBlock response_trailers) {
+ AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
+ std::move(response_headers), response_body,
+ std::move(response_trailers), 0);
+}
+
+void QuicMemoryCacheBackend::AddSpecialResponse(
+ QuicStringPiece host,
+ QuicStringPiece path,
+ SpecialResponseType response_type) {
+ AddResponseImpl(host, path, response_type, SpdyHeaderBlock(), "",
+ SpdyHeaderBlock(), 0);
+}
+
+void QuicMemoryCacheBackend::AddSpecialResponse(
+ QuicStringPiece host,
+ QuicStringPiece path,
+ spdy::SpdyHeaderBlock response_headers,
+ QuicStringPiece response_body,
+ SpecialResponseType response_type) {
+ AddResponseImpl(host, path, response_type, std::move(response_headers),
+ response_body, SpdyHeaderBlock(), 0);
+}
+
+void QuicMemoryCacheBackend::AddStopSendingResponse(
+ QuicStringPiece host,
+ QuicStringPiece path,
+ spdy::SpdyHeaderBlock response_headers,
+ QuicStringPiece response_body,
+ uint16_t stop_sending_code) {
+ AddResponseImpl(host, path, SpecialResponseType::STOP_SENDING,
+ std::move(response_headers), response_body, SpdyHeaderBlock(),
+ stop_sending_code);
+}
+
+QuicMemoryCacheBackend::QuicMemoryCacheBackend() : cache_initialized_(false) {}
+
+bool QuicMemoryCacheBackend::InitializeBackend(
+ const QuicString& cache_directory) {
+ if (cache_directory.empty()) {
+ QUIC_BUG << "cache_directory must not be empty.";
+ return false;
+ }
+ QUIC_LOG(INFO)
+ << "Attempting to initialize QuicMemoryCacheBackend from directory: "
+ << cache_directory;
+ std::vector<QuicString> files = ReadFileContents(cache_directory);
+ std::list<std::unique_ptr<ResourceFile>> resource_files;
+ for (const auto& filename : files) {
+ std::unique_ptr<ResourceFile> resource_file(new ResourceFile(filename));
+
+ // Tease apart filename into host and path.
+ QuicStringPiece base(resource_file->file_name());
+ base.remove_prefix(cache_directory.length());
+ if (base[0] == '/') {
+ base.remove_prefix(1);
+ }
+
+ resource_file->SetHostPathFromBase(base);
+ resource_file->Read();
+
+ AddResponse(resource_file->host(), resource_file->path(),
+ resource_file->spdy_headers().Clone(), resource_file->body());
+
+ resource_files.push_back(std::move(resource_file));
+ }
+
+ for (const auto& resource_file : resource_files) {
+ std::list<ServerPushInfo> push_resources;
+ for (const auto& push_url : resource_file->push_urls()) {
+ QuicUrl url(push_url);
+ const QuicBackendResponse* response = GetResponse(url.host(), url.path());
+ if (!response) {
+ QUIC_BUG << "Push URL '" << push_url << "' not found.";
+ return false;
+ }
+ push_resources.push_back(ServerPushInfo(url, response->headers().Clone(),
+ kV3LowestPriority,
+ (QuicString(response->body()))));
+ }
+ MaybeAddServerPushResources(resource_file->host(), resource_file->path(),
+ push_resources);
+ }
+ cache_initialized_ = true;
+ return true;
+}
+
+bool QuicMemoryCacheBackend::IsBackendInitialized() const {
+ return cache_initialized_;
+}
+
+void QuicMemoryCacheBackend::FetchResponseFromBackend(
+ const SpdyHeaderBlock& request_headers,
+ const QuicString& request_body,
+ QuicSimpleServerBackend::RequestHandler* quic_stream) {
+ const QuicBackendResponse* quic_response = nullptr;
+ // Find response in cache. If not found, send error response.
+ auto authority = request_headers.find(":authority");
+ auto path = request_headers.find(":path");
+ if (authority != request_headers.end() && path != request_headers.end()) {
+ quic_response = GetResponse(authority->second, path->second);
+ }
+
+ QuicString request_url =
+ QuicString(authority->second) + QuicString(path->second);
+ std::list<ServerPushInfo> resources = GetServerPushResources(request_url);
+ QUIC_DVLOG(1)
+ << "Fetching QUIC response from backend in-memory cache for url "
+ << request_url;
+ quic_stream->OnResponseBackendComplete(quic_response, resources);
+}
+
+// The memory cache does not have a per-stream handler
+void QuicMemoryCacheBackend::CloseBackendResponseStream(
+ QuicSimpleServerBackend::RequestHandler* quic_stream) {}
+
+std::list<ServerPushInfo> QuicMemoryCacheBackend::GetServerPushResources(
+ QuicString request_url) {
+ QuicWriterMutexLock lock(&response_mutex_);
+
+ std::list<ServerPushInfo> resources;
+ auto resource_range = server_push_resources_.equal_range(request_url);
+ for (auto it = resource_range.first; it != resource_range.second; ++it) {
+ resources.push_back(it->second);
+ }
+ QUIC_DVLOG(1) << "Found " << resources.size() << " push resources for "
+ << request_url;
+ return resources;
+}
+
+QuicMemoryCacheBackend::~QuicMemoryCacheBackend() {
+ {
+ QuicWriterMutexLock lock(&response_mutex_);
+ responses_.clear();
+ }
+}
+
+void QuicMemoryCacheBackend::AddResponseImpl(QuicStringPiece host,
+ QuicStringPiece path,
+ SpecialResponseType response_type,
+ SpdyHeaderBlock response_headers,
+ QuicStringPiece response_body,
+ SpdyHeaderBlock response_trailers,
+ uint16_t stop_sending_code) {
+ QuicWriterMutexLock lock(&response_mutex_);
+
+ DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\"";
+ QuicString key = GetKey(host, path);
+ if (QuicContainsKey(responses_, key)) {
+ QUIC_BUG << "Response for '" << key << "' already exists!";
+ return;
+ }
+ auto new_response = QuicMakeUnique<QuicBackendResponse>();
+ new_response->set_response_type(response_type);
+ new_response->set_headers(std::move(response_headers));
+ new_response->set_body(response_body);
+ new_response->set_trailers(std::move(response_trailers));
+ new_response->set_stop_sending_code(stop_sending_code);
+ QUIC_DVLOG(1) << "Add response with key " << key;
+ responses_[key] = std::move(new_response);
+}
+
+QuicString QuicMemoryCacheBackend::GetKey(QuicStringPiece host,
+ QuicStringPiece path) const {
+ QuicString host_string = QuicString(host);
+ size_t port = host_string.find(':');
+ if (port != QuicString::npos)
+ host_string = QuicString(host_string.c_str(), port);
+ return host_string + QuicString(path);
+}
+
+void QuicMemoryCacheBackend::MaybeAddServerPushResources(
+ QuicStringPiece request_host,
+ QuicStringPiece request_path,
+ std::list<ServerPushInfo> push_resources) {
+ QuicString request_url = GetKey(request_host, request_path);
+
+ for (const auto& push_resource : push_resources) {
+ if (PushResourceExistsInCache(request_url, push_resource)) {
+ continue;
+ }
+
+ QUIC_DVLOG(1) << "Add request-resource association: request url "
+ << request_url << " push url "
+ << push_resource.request_url.ToString()
+ << " response headers "
+ << push_resource.headers.DebugString();
+ {
+ QuicWriterMutexLock lock(&response_mutex_);
+ server_push_resources_.insert(std::make_pair(request_url, push_resource));
+ }
+ QuicString host = push_resource.request_url.host();
+ if (host.empty()) {
+ host = QuicString(request_host);
+ }
+ QuicString path = push_resource.request_url.path();
+ bool found_existing_response = false;
+ {
+ QuicWriterMutexLock lock(&response_mutex_);
+ found_existing_response = QuicContainsKey(responses_, GetKey(host, path));
+ }
+ if (!found_existing_response) {
+ // Add a server push response to responses map, if it is not in the map.
+ QuicStringPiece body = push_resource.body;
+ QUIC_DVLOG(1) << "Add response for push resource: host " << host
+ << " path " << path;
+ AddResponse(host, path, push_resource.headers.Clone(), body);
+ }
+ }
+}
+
+bool QuicMemoryCacheBackend::PushResourceExistsInCache(
+ QuicString original_request_url,
+ ServerPushInfo resource) {
+ QuicWriterMutexLock lock(&response_mutex_);
+ auto resource_range =
+ server_push_resources_.equal_range(original_request_url);
+ for (auto it = resource_range.first; it != resource_range.second; ++it) {
+ ServerPushInfo push_resource = it->second;
+ if (push_resource.request_url.ToString() ==
+ resource.request_url.ToString()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace quic