blob: 3b2f9e00b57d8ac4a55affb27bc386c8983bf2a6 [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/tools/quic/quic_in_memory_cache.h"
#include <utility>
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/quic/quic_bug_tracker.h"
#include "net/spdy/spdy_http_utils.h"
using base::FilePath;
using base::IntToString;
using base::StringPiece;
using std::list;
using std::string;
namespace net {
namespace {
class ResourceFileImpl : public net::QuicInMemoryCache::ResourceFile {
public:
explicit ResourceFileImpl(const base::FilePath& file_name)
: ResourceFile(file_name) {}
void Read() override {
base::ReadFileToString(FilePath(file_name_), &file_contents_);
int file_len = static_cast<int>(file_contents_.length());
int headers_end =
HttpUtil::LocateEndOfHeaders(file_contents_.data(), file_len);
if (headers_end < 1) {
LOG(DFATAL) << "Headers invalid or empty, ignoring: "
<< file_name_.value();
return;
}
http_headers_ = new HttpResponseHeaders(
HttpUtil::AssembleRawHeaders(file_contents_.data(), headers_end));
if (http_headers_->GetNormalizedHeader("X-Original-Url", &url_)) {
x_original_url_ = StringPiece(url_);
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/.
StringPiece x_push_url("X-Push-Url");
if (http_headers_->HasHeader(x_push_url)) {
size_t iter = 0;
std::unique_ptr<string> push_url(new string());
while (
http_headers_->EnumerateHeader(&iter, x_push_url, push_url.get())) {
push_urls_.push_back(StringPiece(*push_url));
push_url_values_.push_back(std::move(push_url));
push_url.reset(new string());
}
}
body_ = StringPiece(file_contents_.data() + headers_end,
file_contents_.size() - headers_end);
CreateSpdyHeadersFromHttpResponse(*http_headers_, HTTP2, &spdy_headers_);
}
private:
scoped_refptr<HttpResponseHeaders> http_headers_;
string url_;
list<std::unique_ptr<string>> push_url_values_;
DISALLOW_COPY_AND_ASSIGN(ResourceFileImpl);
};
} // namespace
QuicInMemoryCache::ServerPushInfo::ServerPushInfo(GURL request_url,
SpdyHeaderBlock headers,
net::SpdyPriority priority,
string body)
: request_url(request_url),
headers(std::move(headers)),
priority(priority),
body(body) {}
QuicInMemoryCache::ServerPushInfo::ServerPushInfo(const ServerPushInfo& other)
: request_url(other.request_url),
headers(other.headers.Clone()),
priority(other.priority),
body(other.body) {}
QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE) {}
QuicInMemoryCache::Response::~Response() {}
QuicInMemoryCache::ResourceFile::ResourceFile(const base::FilePath& file_name)
: file_name_(file_name), file_name_string_(file_name.AsUTF8Unsafe()) {}
QuicInMemoryCache::ResourceFile::~ResourceFile() {}
void QuicInMemoryCache::ResourceFile::SetHostPathFromBase(StringPiece 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);
}
}
StringPiece QuicInMemoryCache::ResourceFile::RemoveScheme(StringPiece url) {
if (url.starts_with("https://")) {
url.remove_prefix(8);
} else if (url.starts_with("http://")) {
url.remove_prefix(7);
}
return url;
}
void QuicInMemoryCache::ResourceFile::HandleXOriginalUrl() {
StringPiece url(x_original_url_);
// Remove the protocol so we can add it below.
url = RemoveScheme(url);
SetHostPathFromBase(url);
}
// static
QuicInMemoryCache* QuicInMemoryCache::GetInstance() {
return base::Singleton<QuicInMemoryCache>::get();
}
const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse(
StringPiece host,
StringPiece path) const {
ResponseMap::const_iterator it = responses_.find(GetKey(host, path));
if (it == responses_.end()) {
DVLOG(1) << "Get response for resource failed: host " << host << " path "
<< path;
if (default_response_.get()) {
return default_response_.get();
}
return nullptr;
}
return it->second;
}
typedef QuicInMemoryCache::ServerPushInfo ServerPushInfo;
void QuicInMemoryCache::AddSimpleResponse(StringPiece host,
StringPiece path,
int response_code,
StringPiece body) {
SpdyHeaderBlock response_headers;
response_headers[":status"] = IntToString(response_code);
response_headers["content-length"] =
IntToString(static_cast<int>(body.length()));
AddResponse(host, path, std::move(response_headers), body);
}
void QuicInMemoryCache::AddSimpleResponseWithServerPushResources(
StringPiece host,
StringPiece path,
int response_code,
StringPiece body,
list<ServerPushInfo> push_resources) {
AddSimpleResponse(host, path, response_code, body);
MaybeAddServerPushResources(host, path, push_resources);
}
void QuicInMemoryCache::AddDefaultResponse(Response* response) {
default_response_.reset(response);
}
void QuicInMemoryCache::AddResponse(StringPiece host,
StringPiece path,
SpdyHeaderBlock response_headers,
StringPiece response_body) {
AddResponseImpl(host, path, REGULAR_RESPONSE, std::move(response_headers),
response_body, SpdyHeaderBlock());
}
void QuicInMemoryCache::AddResponse(StringPiece host,
StringPiece path,
SpdyHeaderBlock response_headers,
StringPiece response_body,
SpdyHeaderBlock response_trailers) {
AddResponseImpl(host, path, REGULAR_RESPONSE, std::move(response_headers),
response_body, std::move(response_trailers));
}
void QuicInMemoryCache::AddSpecialResponse(StringPiece host,
StringPiece path,
SpecialResponseType response_type) {
AddResponseImpl(host, path, response_type, SpdyHeaderBlock(), "",
SpdyHeaderBlock());
}
QuicInMemoryCache::QuicInMemoryCache() {}
void QuicInMemoryCache::ResetForTests() {
STLDeleteValues(&responses_);
server_push_resources_.clear();
}
void QuicInMemoryCache::InitializeFromDirectory(const string& cache_directory) {
if (cache_directory.empty()) {
QUIC_BUG << "cache_directory must not be empty.";
return;
}
VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
<< cache_directory;
FilePath directory(FilePath::FromUTF8Unsafe(cache_directory));
base::FileEnumerator file_list(directory, true, base::FileEnumerator::FILES);
list<std::unique_ptr<ResourceFile>> resource_files;
for (FilePath file_iter = file_list.Next(); !file_iter.empty();
file_iter = file_list.Next()) {
// Need to skip files in .svn directories
if (file_iter.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos) {
continue;
}
std::unique_ptr<ResourceFile> resource_file(
new ResourceFileImpl(file_iter));
// Tease apart filename into host and path.
StringPiece 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) {
list<ServerPushInfo> push_resources;
for (const auto& push_url : resource_file->push_urls()) {
GURL url(push_url);
const Response* response = GetResponse(url.host(), url.path());
if (!response) {
QUIC_BUG << "Push URL '" << push_url << "' not found.";
return;
}
push_resources.push_back(ServerPushInfo(url, response->headers().Clone(),
net::kV3LowestPriority,
response->body().as_string()));
}
MaybeAddServerPushResources(resource_file->host(), resource_file->path(),
push_resources);
}
}
list<ServerPushInfo> QuicInMemoryCache::GetServerPushResources(
string request_url) {
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);
}
DVLOG(1) << "Found " << resources.size() << " push resources for "
<< request_url;
return resources;
}
QuicInMemoryCache::~QuicInMemoryCache() {
STLDeleteValues(&responses_);
}
void QuicInMemoryCache::AddResponseImpl(StringPiece host,
StringPiece path,
SpecialResponseType response_type,
SpdyHeaderBlock response_headers,
StringPiece response_body,
SpdyHeaderBlock response_trailers) {
DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\"";
string key = GetKey(host, path);
if (ContainsKey(responses_, key)) {
QUIC_BUG << "Response for '" << key << "' already exists!";
return;
}
Response* new_response = new Response();
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));
DVLOG(1) << "Add response with key " << key;
responses_[key] = new_response;
}
string QuicInMemoryCache::GetKey(StringPiece host, StringPiece path) const {
return host.as_string() + path.as_string();
}
void QuicInMemoryCache::MaybeAddServerPushResources(
StringPiece request_host,
StringPiece request_path,
list<ServerPushInfo> push_resources) {
string request_url = GetKey(request_host, request_path);
for (const auto& push_resource : push_resources) {
if (PushResourceExistsInCache(request_url, push_resource)) {
continue;
}
DVLOG(1) << "Add request-resource association: request url " << request_url
<< " push url " << push_resource.request_url
<< " response headers " << push_resource.headers.DebugString();
server_push_resources_.insert(std::make_pair(request_url, push_resource));
string host = push_resource.request_url.host();
if (host.empty()) {
host = request_host.as_string();
}
string path = push_resource.request_url.path();
if (responses_.find(GetKey(host, path)) == responses_.end()) {
// Add a server push response to responses map, if it is not in the map.
StringPiece body = push_resource.body;
DVLOG(1) << "Add response for push resource: host " << host << " path "
<< path;
AddResponse(host, path, push_resource.headers.Clone(), body);
}
}
}
bool QuicInMemoryCache::PushResourceExistsInCache(string original_request_url,
ServerPushInfo resource) {
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.spec() == resource.request_url.spec()) {
return true;
}
}
return false;
}
} // namespace net