blob: 6452d22c5ededeb17d3356a1f6bd291ba69d6c8e [file] [log] [blame]
// 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_text_utils.h"
using spdy::kV3LowestPriority;
using spdy::SpdyHeaderBlock;
namespace quic {
QuicMemoryCacheBackend::ResourceFile::ResourceFile(const std::string& 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 == std::string::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 == std::string::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 == std::string::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 == std::string::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()) {
uint64_t ignored = 0;
if (generate_bytes_response_) {
if (QuicTextUtils::StringToUint64(
QuicStringPiece(path.data() + 1, path.size() - 1), &ignored)) {
// The actual parsed length is ignored here and will be recomputed
// by the caller.
return generate_bytes_response_.get();
}
}
QUIC_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 std::string& 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<std::string> 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,
(std::string(response->body()))));
}
MaybeAddServerPushResources(resource_file->host(), resource_file->path(),
push_resources);
}
cache_initialized_ = true;
return true;
}
void QuicMemoryCacheBackend::GenerateDynamicResponses() {
QuicWriterMutexLock lock(&response_mutex_);
// Add a generate bytes response.
spdy::SpdyHeaderBlock response_headers;
response_headers[":version"] = "HTTP/1.1";
response_headers[":status"] = "200";
generate_bytes_response_ = std::make_unique<QuicBackendResponse>();
generate_bytes_response_->set_headers(std::move(response_headers));
generate_bytes_response_->set_response_type(
QuicBackendResponse::GENERATE_BYTES);
}
bool QuicMemoryCacheBackend::IsBackendInitialized() const {
return cache_initialized_;
}
void QuicMemoryCacheBackend::FetchResponseFromBackend(
const SpdyHeaderBlock& request_headers,
const std::string& /*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);
}
std::string request_url =
std::string(authority->second) + std::string(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(
std::string 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\"";
std::string key = GetKey(host, path);
if (QuicContainsKey(responses_, key)) {
QUIC_BUG << "Response for '" << key << "' already exists!";
return;
}
auto new_response = std::make_unique<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);
}
std::string QuicMemoryCacheBackend::GetKey(QuicStringPiece host,
QuicStringPiece path) const {
std::string host_string = std::string(host);
size_t port = host_string.find(':');
if (port != std::string::npos)
host_string = std::string(host_string.c_str(), port);
return host_string + std::string(path);
}
void QuicMemoryCacheBackend::MaybeAddServerPushResources(
QuicStringPiece request_host,
QuicStringPiece request_path,
std::list<ServerPushInfo> push_resources) {
std::string 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));
}
std::string host = push_resource.request_url.host();
if (host.empty()) {
host = std::string(request_host);
}
std::string 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(
std::string 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