blob: 45d25b24e407759bd39de8a4fe923d6a2f759d43 [file] [log] [blame]
[email protected]ad83abaa2014-06-17 01:08:461// Copyright 2014 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.
4
5#include "net/quic/quic_in_memory_cache.h"
6
7#include "base/file_util.h"
8#include "base/files/file_enumerator.h"
9#include "base/stl_util.h"
10#include "base/strings/string_number_conversions.h"
11#include "net/tools/balsa/balsa_headers.h"
12
13using base::FilePath;
14using base::StringPiece;
15using std::string;
16
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>
20
21namespace net {
22
23namespace {
24
25const FilePath::CharType* g_quic_in_memory_cache_dir = FILE_PATH_LITERAL("");
26
27// BalsaVisitor implementation (glue) which caches response bodies.
28class CachingBalsaVisitor : public NoOpBalsaVisitor {
29 public:
30 CachingBalsaVisitor() : done_framing_(false) {}
31 virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE {
32 AppendToBody(input, size);
33 }
34 virtual void MessageDone() OVERRIDE {
35 done_framing_ = true;
36 }
37 virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE {
38 UnhandledError();
39 }
40 virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {
41 UnhandledError();
42 }
43 virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE {
44 UnhandledError();
45 }
46 virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE {
47 UnhandledError();
48 }
49 void UnhandledError() {
50 LOG(DFATAL) << "Unhandled error framing HTTP.";
51 }
52 void AppendToBody(const char* input, size_t size) {
53 body_.append(input, size);
54 }
55 bool done_framing() const { return done_framing_; }
56 const string& body() const { return body_; }
57
58 private:
59 bool done_framing_;
60 string body_;
61};
62
63} // namespace
64
65// static
66QuicInMemoryCache* QuicInMemoryCache::GetInstance() {
67 return Singleton<QuicInMemoryCache>::get();
68}
69
70const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse(
71 const BalsaHeaders& request_headers) const {
72 ResponseMap::const_iterator it = responses_.find(GetKey(request_headers));
73 if (it == responses_.end()) {
74 return NULL;
75 }
76 return it->second;
77}
78
79void QuicInMemoryCache::AddSimpleResponse(StringPiece method,
80 StringPiece path,
81 StringPiece version,
82 StringPiece response_code,
83 StringPiece response_detail,
84 StringPiece body) {
85 BalsaHeaders request_headers, response_headers;
86 request_headers.SetRequestFirstlineFromStringPieces(method,
87 path,
88 version);
89 response_headers.SetRequestFirstlineFromStringPieces(version,
90 response_code,
91 response_detail);
92 response_headers.AppendHeader("content-length",
93 base::IntToString(body.length()));
94
95 AddResponse(request_headers, response_headers, body);
96}
97
98void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers,
99 const BalsaHeaders& response_headers,
100 StringPiece response_body) {
101 VLOG(1) << "Adding response for: " << GetKey(request_headers);
102 if (ContainsKey(responses_, GetKey(request_headers))) {
103 LOG(DFATAL) << "Response for given request already exists!";
104 return;
105 }
106 Response* new_response = new Response();
107 new_response->set_headers(response_headers);
108 new_response->set_body(response_body);
109 responses_[GetKey(request_headers)] = new_response;
110}
111
112void QuicInMemoryCache::AddSpecialResponse(StringPiece method,
113 StringPiece path,
114 StringPiece version,
115 SpecialResponseType response_type) {
116 BalsaHeaders request_headers, response_headers;
117 request_headers.SetRequestFirstlineFromStringPieces(method,
118 path,
119 version);
120 AddResponse(request_headers, response_headers, "");
121 responses_[GetKey(request_headers)]->response_type_ = response_type;
122}
123
124QuicInMemoryCache::QuicInMemoryCache() {
125 Initialize();
126}
127
128void QuicInMemoryCache::ResetForTests() {
129 STLDeleteValues(&responses_);
130 Initialize();
131}
132
133void QuicInMemoryCache::Initialize() {
134 // If there's no defined cache dir, we have no initialization to do.
135 if (g_quic_in_memory_cache_dir[0] == '\0') {
136 VLOG(1) << "No cache directory found. Skipping initialization.";
137 return;
138 }
139 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
140 << g_quic_in_memory_cache_dir;
141
142 FilePath directory(g_quic_in_memory_cache_dir);
143 base::FileEnumerator file_list(directory,
144 true,
145 base::FileEnumerator::FILES);
146
147 FilePath file = file_list.Next();
148 while (!file.empty()) {
149 // Need to skip files in .svn directories
150 if (file.value().find(FILE_PATH_LITERAL("/.svn/")) != std::string::npos) {
151 file = file_list.Next();
152 continue;
153 }
154
155 BalsaHeaders request_headers, response_headers;
156
157 string file_contents;
158 base::ReadFileToString(file, &file_contents);
159
160 // Frame HTTP.
161 CachingBalsaVisitor caching_visitor;
162 BalsaFrame framer;
163 framer.set_balsa_headers(&response_headers);
164 framer.set_balsa_visitor(&caching_visitor);
165 size_t processed = 0;
166 while (processed < file_contents.length() &&
167 !caching_visitor.done_framing()) {
168 processed += framer.ProcessInput(file_contents.c_str() + processed,
169 file_contents.length() - processed);
170 }
171
172 string response_headers_str;
173 response_headers.DumpToString(&response_headers_str);
174 if (!caching_visitor.done_framing()) {
175 LOG(DFATAL) << "Did not frame entire message from file: " << file.value()
176 << " (" << processed << " of " << file_contents.length()
177 << " bytes).";
178 }
179 if (processed < file_contents.length()) {
180 // Didn't frame whole file. Assume remainder is body.
181 // This sometimes happens as a result of incompatibilities between
182 // BalsaFramer and wget's serialization of HTTP sans content-length.
183 caching_visitor.AppendToBody(file_contents.c_str() + processed,
184 file_contents.length() - processed);
185 processed += file_contents.length();
186 }
187
188 StringPiece base = file.AsUTF8Unsafe();
189 if (response_headers.HasHeader("X-Original-Url")) {
190 base = response_headers.GetHeader("X-Original-Url");
191 response_headers.RemoveAllOfHeader("X-Original-Url");
192 // Remove the protocol so that the string is of the form host + path,
193 // which is parsed properly below.
194 if (StringPieceUtils::StartsWithIgnoreCase(base, "https://")) {
195 base.remove_prefix(8);
196 } else if (StringPieceUtils::StartsWithIgnoreCase(base, "http://")) {
197 base.remove_prefix(7);
198 }
199 }
200 int path_start = base.find_first_of('/');
201 DCHECK_LT(0, path_start);
202 StringPiece host(base.substr(0, path_start));
203 StringPiece path(base.substr(path_start));
204 if (path[path.length() - 1] == ',') {
205 path.remove_suffix(1);
206 }
207 // Set up request headers. Assume method is GET and protocol is HTTP/1.1.
208 request_headers.SetRequestFirstlineFromStringPieces("GET",
209 path,
210 "HTTP/1.1");
211 request_headers.ReplaceOrAppendHeader("host", host);
212
213 VLOG(1) << "Inserting 'http://" << GetKey(request_headers)
214 << "' into QuicInMemoryCache.";
215
216 AddResponse(request_headers, response_headers, caching_visitor.body());
217
218 file = file_list.Next();
219 }
220}
221
222QuicInMemoryCache::~QuicInMemoryCache() {
223 STLDeleteValues(&responses_);
224}
225
226string QuicInMemoryCache::GetKey(const BalsaHeaders& request_headers) const {
227 StringPiece uri = request_headers.request_uri();
228 if (uri.size() == 0) {
229 return "";
230 }
231 StringPiece host;
232 if (uri[0] == '/') {
233 host = request_headers.GetHeader("host");
234 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) {
235 uri.remove_prefix(8);
236 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) {
237 uri.remove_prefix(7);
238 }
239 return host.as_string() + uri.as_string();
240}
241
242} // namespace net