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
3 // found in the LICENSE file.
5 #include "net/tools/quic/quic_in_memory_cache.h"
7 #include "base/file_util.h"
8 #include "base/files/file_enumerator.h"
9 #include "base/stl_util.h"
12 using base::StringPiece
;
15 // Specifies the directory used during QuicInMemoryCache
16 // construction to seed the cache. Cache directory can be
17 // generated using `wget -p --save-headers <url>
22 std::string FLAGS_quic_in_memory_cache_dir
= "/tmp/quic-data";
26 // BalsaVisitor implementation (glue) which caches response bodies.
27 class CachingBalsaVisitor
: public BalsaVisitorInterface
{
29 CachingBalsaVisitor() : done_framing_(false) {}
30 virtual void ProcessBodyData(const char* input
, size_t size
) OVERRIDE
{
31 AppendToBody(input
, size
);
33 virtual void ProcessTrailers(const BalsaHeaders
& trailer
) {
34 LOG(DFATAL
) << "Trailers not supported.";
36 virtual void MessageDone() OVERRIDE
{
39 virtual void HandleHeaderError(BalsaFrame
* framer
) OVERRIDE
{
42 virtual void HandleHeaderWarning(BalsaFrame
* framer
) OVERRIDE
{
45 virtual void HandleTrailerError(BalsaFrame
* framer
) { UnhandledError(); }
46 virtual void HandleTrailerWarning(BalsaFrame
* framer
) { UnhandledError(); }
47 virtual void HandleChunkingError(BalsaFrame
* framer
) OVERRIDE
{
50 virtual void HandleBodyError(BalsaFrame
* framer
) OVERRIDE
{
53 void UnhandledError() {
54 LOG(DFATAL
) << "Unhandled error framing HTTP.";
56 virtual void ProcessBodyInput(const char*, size_t) OVERRIDE
{}
57 virtual void ProcessHeaderInput(const char*, size_t) OVERRIDE
{}
58 virtual void ProcessTrailerInput(const char*, size_t) OVERRIDE
{}
59 virtual void ProcessHeaders(const net::BalsaHeaders
&) OVERRIDE
{}
60 virtual void ProcessRequestFirstLine(
61 const char*, size_t, const char*, size_t,
62 const char*, size_t, const char*, size_t) OVERRIDE
{}
63 virtual void ProcessResponseFirstLine(
64 const char*, size_t, const char*,
65 size_t, const char*, size_t, const char*, size_t) OVERRIDE
{}
66 virtual void ProcessChunkLength(size_t) OVERRIDE
{}
67 virtual void ProcessChunkExtensions(const char*, size_t) OVERRIDE
{}
68 virtual void HeaderDone() OVERRIDE
{}
70 void AppendToBody(const char* input
, size_t size
) {
71 body_
.append(input
, size
);
73 bool done_framing() const { return done_framing_
; }
74 const string
& body() const { return body_
; }
83 QuicInMemoryCache
* QuicInMemoryCache::GetInstance() {
84 return Singleton
<QuicInMemoryCache
>::get();
87 const QuicInMemoryCache::Response
* QuicInMemoryCache::GetResponse(
88 const BalsaHeaders
& request_headers
) const {
89 ResponseMap::const_iterator it
= responses_
.find(GetKey(request_headers
));
90 if (it
== responses_
.end()) {
96 void QuicInMemoryCache::AddResponse(const BalsaHeaders
& request_headers
,
97 const BalsaHeaders
& response_headers
,
98 StringPiece response_body
) {
99 LOG(INFO
) << "Adding response for: " << GetKey(request_headers
);
100 if (ContainsKey(responses_
, GetKey(request_headers
))) {
101 LOG(DFATAL
) << "Response for given request already exists!";
103 Response
* new_response
= new Response();
104 new_response
->set_headers(response_headers
);
105 new_response
->set_body(response_body
);
106 responses_
[GetKey(request_headers
)] = new_response
;
109 void QuicInMemoryCache::ResetForTests() {
110 STLDeleteValues(&responses_
);
114 QuicInMemoryCache::QuicInMemoryCache() {
118 void QuicInMemoryCache::Initialize() {
119 // If there's no defined cache dir, we have no initialization to do.
120 if (FLAGS_quic_in_memory_cache_dir
.empty()) {
121 LOG(WARNING
) << "No cache directory found. Skipping initialization.";
124 LOG(INFO
) << "Attempting to initialize QuicInMemoryCache from directory: "
125 << FLAGS_quic_in_memory_cache_dir
;
127 FilePath
directory(FLAGS_quic_in_memory_cache_dir
);
128 base::FileEnumerator
file_list(directory
,
130 base::FileEnumerator::FILES
);
132 FilePath file
= file_list
.Next();
133 while (!file
.empty()) {
134 // Need to skip files in .svn directories
135 if (file
.value().find("/.svn/") != std::string::npos
) {
136 file
= file_list
.Next();
140 BalsaHeaders request_headers
, response_headers
;
142 string file_contents
;
143 file_util::ReadFileToString(file
, &file_contents
);
146 CachingBalsaVisitor caching_visitor
;
148 framer
.set_balsa_headers(&response_headers
);
149 framer
.set_balsa_visitor(&caching_visitor
);
150 size_t processed
= 0;
151 while (processed
< file_contents
.length() &&
152 !caching_visitor
.done_framing()) {
153 processed
+= framer
.ProcessInput(file_contents
.c_str() + processed
,
154 file_contents
.length() - processed
);
157 string response_headers_str
;
158 response_headers
.DumpToString(&response_headers_str
);
159 if (!caching_visitor
.done_framing()) {
160 LOG(DFATAL
) << "Did not frame entire message from file: " << file
.value()
161 << " (" << processed
<< " of " << file_contents
.length()
164 if (processed
< file_contents
.length()) {
165 // Didn't frame whole file. Assume remainder is body.
166 // This sometimes happens as a result of incompatibilities between
167 // BalsaFramer and wget's serialization of HTTP sans content-length.
168 caching_visitor
.AppendToBody(file_contents
.c_str() + processed
,
169 file_contents
.length() - processed
);
170 processed
+= file_contents
.length();
173 StringPiece base
= file
.value();
174 if (response_headers
.HasHeader("X-Original-Url")) {
175 base
= response_headers
.GetHeader("X-Original-Url");
176 response_headers
.RemoveAllOfHeader("X-Original-Url");
177 // Remove the protocol so that the string is of the form host + path,
178 // which is parsed properly below.
179 if (StringPieceUtils::StartsWithIgnoreCase(base
, "https://")) {
180 base
.remove_prefix(8);
181 } else if (StringPieceUtils::StartsWithIgnoreCase(base
, "http://")) {
182 base
.remove_prefix(7);
185 int path_start
= base
.find_first_of('/');
186 DCHECK_LT(0, path_start
);
187 StringPiece
host(base
.substr(0, path_start
));
188 StringPiece
path(base
.substr(path_start
));
189 if (path
[path
.length() - 1] == ',') {
190 path
.remove_suffix(1);
192 // Set up request headers. Assume method is GET and protocol is HTTP/1.1.
193 request_headers
.SetRequestFirstlineFromStringPieces("GET",
196 request_headers
.ReplaceOrAppendHeader("host", host
);
198 LOG(INFO
) << "Inserting 'http://" << GetKey(request_headers
)
199 << "' into QuicInMemoryCache.";
201 AddResponse(request_headers
, response_headers
, caching_visitor
.body());
203 file
= file_list
.Next();
207 QuicInMemoryCache::~QuicInMemoryCache() {
208 STLDeleteValues(&responses_
);
211 string
QuicInMemoryCache::GetKey(const BalsaHeaders
& request_headers
) const {
212 StringPiece uri
= request_headers
.request_uri();
215 host
= request_headers
.GetHeader("host");
216 } else if (StringPieceUtils::StartsWithIgnoreCase(uri
, "https://")) {
217 uri
.remove_prefix(8);
218 } else if (StringPieceUtils::StartsWithIgnoreCase(uri
, "http://")) {
219 uri
.remove_prefix(7);
221 return host
.as_string() + uri
.as_string();