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/files/file_enumerator.h"
8 #include "base/files/file_util.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "net/tools/balsa/balsa_headers.h"
14 using base::StringPiece
;
17 // Specifies the directory used during QuicInMemoryCache
18 // construction to seed the cache. Cache directory can be
19 // generated using `wget -p --save-headers <url>
24 std::string FLAGS_quic_in_memory_cache_dir
= "";
28 // BalsaVisitor implementation (glue) which caches response bodies.
29 class CachingBalsaVisitor
: public NoOpBalsaVisitor
{
31 CachingBalsaVisitor() : done_framing_(false) {}
32 void ProcessBodyData(const char* input
, size_t size
) override
{
33 AppendToBody(input
, size
);
35 void MessageDone() override
{ done_framing_
= true; }
36 void HandleHeaderError(BalsaFrame
* framer
) override
{ UnhandledError(); }
37 void HandleHeaderWarning(BalsaFrame
* framer
) override
{ UnhandledError(); }
38 void HandleChunkingError(BalsaFrame
* framer
) override
{ UnhandledError(); }
39 void HandleBodyError(BalsaFrame
* framer
) override
{ UnhandledError(); }
40 void UnhandledError() {
41 LOG(DFATAL
) << "Unhandled error framing HTTP.";
43 void AppendToBody(const char* input
, size_t size
) {
44 body_
.append(input
, size
);
46 bool done_framing() const { return done_framing_
; }
47 const string
& body() const { return body_
; }
57 QuicInMemoryCache
* QuicInMemoryCache::GetInstance() {
58 return Singleton
<QuicInMemoryCache
>::get();
61 const QuicInMemoryCache::Response
* QuicInMemoryCache::GetResponse(
62 const BalsaHeaders
& request_headers
) const {
63 ResponseMap::const_iterator it
= responses_
.find(GetKey(request_headers
));
64 if (it
== responses_
.end()) {
70 void QuicInMemoryCache::AddSimpleResponse(StringPiece method
,
73 StringPiece response_code
,
74 StringPiece response_detail
,
76 BalsaHeaders request_headers
, response_headers
;
77 request_headers
.SetRequestFirstlineFromStringPieces(method
,
80 response_headers
.SetRequestFirstlineFromStringPieces(version
,
83 response_headers
.AppendHeader("content-length",
84 base::IntToString(body
.length()));
86 AddResponse(request_headers
, response_headers
, body
);
89 void QuicInMemoryCache::AddResponse(const BalsaHeaders
& request_headers
,
90 const BalsaHeaders
& response_headers
,
91 StringPiece response_body
) {
92 VLOG(1) << "Adding response for: " << GetKey(request_headers
);
93 if (ContainsKey(responses_
, GetKey(request_headers
))) {
94 LOG(DFATAL
) << "Response for given request already exists!";
97 Response
* new_response
= new Response();
98 new_response
->set_headers(response_headers
);
99 new_response
->set_body(response_body
);
100 responses_
[GetKey(request_headers
)] = new_response
;
103 void QuicInMemoryCache::AddSpecialResponse(StringPiece method
,
106 SpecialResponseType response_type
) {
107 BalsaHeaders request_headers
, response_headers
;
108 request_headers
.SetRequestFirstlineFromStringPieces(method
,
111 AddResponse(request_headers
, response_headers
, "");
112 responses_
[GetKey(request_headers
)]->response_type_
= response_type
;
115 QuicInMemoryCache::QuicInMemoryCache() {
119 void QuicInMemoryCache::ResetForTests() {
120 STLDeleteValues(&responses_
);
124 void QuicInMemoryCache::Initialize() {
125 // If there's no defined cache dir, we have no initialization to do.
126 if (FLAGS_quic_in_memory_cache_dir
.empty()) {
127 VLOG(1) << "No cache directory found. Skipping initialization.";
130 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
131 << FLAGS_quic_in_memory_cache_dir
;
133 FilePath
directory(FLAGS_quic_in_memory_cache_dir
);
134 base::FileEnumerator
file_list(directory
,
136 base::FileEnumerator::FILES
);
138 FilePath file
= file_list
.Next();
139 while (!file
.empty()) {
140 // Need to skip files in .svn directories
141 if (file
.value().find("/.svn/") != std::string::npos
) {
142 file
= file_list
.Next();
146 BalsaHeaders request_headers
, response_headers
;
148 string file_contents
;
149 base::ReadFileToString(file
, &file_contents
);
152 CachingBalsaVisitor caching_visitor
;
154 framer
.set_balsa_headers(&response_headers
);
155 framer
.set_balsa_visitor(&caching_visitor
);
156 size_t processed
= 0;
157 while (processed
< file_contents
.length() &&
158 !caching_visitor
.done_framing()) {
159 processed
+= framer
.ProcessInput(file_contents
.c_str() + processed
,
160 file_contents
.length() - processed
);
163 if (!caching_visitor
.done_framing()) {
164 LOG(DFATAL
) << "Did not frame entire message from file: " << file
.value()
165 << " (" << processed
<< " of " << file_contents
.length()
168 if (processed
< file_contents
.length()) {
169 // Didn't frame whole file. Assume remainder is body.
170 // This sometimes happens as a result of incompatibilities between
171 // BalsaFramer and wget's serialization of HTTP sans content-length.
172 caching_visitor
.AppendToBody(file_contents
.c_str() + processed
,
173 file_contents
.length() - processed
);
174 processed
+= file_contents
.length();
177 StringPiece base
= file
.value();
178 if (response_headers
.HasHeader("X-Original-Url")) {
179 base
= response_headers
.GetHeader("X-Original-Url");
180 response_headers
.RemoveAllOfHeader("X-Original-Url");
181 // Remove the protocol so that the string is of the form host + path,
182 // which is parsed properly below.
183 if (StringPieceUtils::StartsWithIgnoreCase(base
, "https://")) {
184 base
.remove_prefix(8);
185 } else if (StringPieceUtils::StartsWithIgnoreCase(base
, "http://")) {
186 base
.remove_prefix(7);
189 int path_start
= base
.find_first_of('/');
190 DCHECK_LT(0, path_start
);
191 StringPiece
host(base
.substr(0, path_start
));
192 StringPiece
path(base
.substr(path_start
));
193 if (path
[path
.length() - 1] == ',') {
194 path
.remove_suffix(1);
196 // Set up request headers. Assume method is GET and protocol is HTTP/1.1.
197 request_headers
.SetRequestFirstlineFromStringPieces("GET",
200 request_headers
.ReplaceOrAppendHeader("host", host
);
202 VLOG(1) << "Inserting 'http://" << GetKey(request_headers
)
203 << "' into QuicInMemoryCache.";
205 AddResponse(request_headers
, response_headers
, caching_visitor
.body());
207 file
= file_list
.Next();
211 QuicInMemoryCache::~QuicInMemoryCache() {
212 STLDeleteValues(&responses_
);
215 string
QuicInMemoryCache::GetKey(const BalsaHeaders
& request_headers
) const {
216 StringPiece uri
= request_headers
.request_uri();
217 if (uri
.size() == 0) {
222 host
= request_headers
.GetHeader("host");
223 } else if (StringPieceUtils::StartsWithIgnoreCase(uri
, "https://")) {
224 uri
.remove_prefix(8);
225 } else if (StringPieceUtils::StartsWithIgnoreCase(uri
, "http://")) {
226 uri
.remove_prefix(7);
228 return host
.as_string() + uri
.as_string();